diff --git a/pom.xml b/pom.xml index a1c317b..8e46a66 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 com.instaclustr - ic-sstable-tools-4.1.3 + ic-sstable-tools-5.0.0 1.0.0 Instaclustr SSTable Tools @@ -21,7 +21,7 @@ 3.1.1 3.1.1 3.8.1 - 2.2.4 + 4.9.10 Various <support@instaclustr.com> 1.8 @@ -34,9 +34,9 @@ ${project.build.directory} UTF-8 - 1.8 - 1.8 - 8 + 11 + 11 + 11 2018 @@ -83,12 +83,12 @@ info.picocli picocli - 4.5.2 + 4.7.1 org.apache.cassandra cassandra-all - 4.1.3 + 5.0-beta1 provided @@ -109,7 +109,7 @@ - 8 + ${java.version} @@ -147,13 +147,21 @@ ${maven.git.command.plugin.version} + get-the-git-infos revision + initialize - ${project.basedir}/.git + true + ${project.build.outputDirectory}/git.properties + + ^git.build.(time|version)$ + ^git.commit.id.(abbrev|full)$ + + full diff --git a/src/main/java/com/instaclustr/sstabletools/ColumnFamilyProxy.java b/src/main/java/com/instaclustr/sstabletools/ColumnFamilyProxy.java index cfc976b..370e9c2 100644 --- a/src/main/java/com/instaclustr/sstabletools/ColumnFamilyProxy.java +++ b/src/main/java/com/instaclustr/sstabletools/ColumnFamilyProxy.java @@ -1,6 +1,5 @@ package com.instaclustr.sstabletools; -import com.google.common.util.concurrent.RateLimiter; import org.apache.cassandra.db.DecoratedKey; import java.util.Collection; @@ -39,13 +38,6 @@ public interface ColumnFamilyProxy extends AutoCloseable { */ String formatKey(DecoratedKey key); - /** - * Is the column family using Date Tiered compaction strategy. - * - * @return True if column family is using Date Tiered compaction strategy. - */ - boolean isDTCS(); - /** * Is the column family using Time Window compaction strategy. * diff --git a/src/main/java/com/instaclustr/sstabletools/PurgeStatisticsCollector.java b/src/main/java/com/instaclustr/sstabletools/PurgeStatisticsCollector.java index 6f37069..f4c556d 100644 --- a/src/main/java/com/instaclustr/sstabletools/PurgeStatisticsCollector.java +++ b/src/main/java/com/instaclustr/sstabletools/PurgeStatisticsCollector.java @@ -29,7 +29,7 @@ public class PurgeStatisticsCollector implements Runnable { @Option(names = {"-t"}, description = "Snapshot name", arity = "1") public String snapshotName; - @Option(names = {"-f"}, description = "Filter to sstables (comma separated", defaultValue = "") + @Option(names = {"-f"}, description = "Filter to sstables (comma separated)", defaultValue = "") public String filters; @Option(names = {"-b"}, description = "Batch mode", arity = "0") diff --git a/src/main/java/com/instaclustr/sstabletools/SSTableMetadata.java b/src/main/java/com/instaclustr/sstabletools/SSTableMetadata.java index 0b25444..9abdb75 100644 --- a/src/main/java/com/instaclustr/sstabletools/SSTableMetadata.java +++ b/src/main/java/com/instaclustr/sstabletools/SSTableMetadata.java @@ -55,9 +55,9 @@ public int compare(SSTableMetadata o1, SSTableMetadata o2) { public long maxTimestamp; - public int minLocalDeletionTime; + public long minLocalDeletionTime; - public int maxLocalDeletionTime; + public long maxLocalDeletionTime; public long fileTimestamp; diff --git a/src/main/java/com/instaclustr/sstabletools/cassandra/CassandraBackend.java b/src/main/java/com/instaclustr/sstabletools/cassandra/CassandraBackend.java index a3da7e1..fe1daea 100644 --- a/src/main/java/com/instaclustr/sstabletools/cassandra/CassandraBackend.java +++ b/src/main/java/com/instaclustr/sstabletools/cassandra/CassandraBackend.java @@ -3,11 +3,11 @@ import com.instaclustr.sstabletools.CassandraProxy; import com.instaclustr.sstabletools.ColumnFamilyProxy; import com.instaclustr.sstabletools.SSTableMetadata; +import org.apache.cassandra.io.sstable.format.SSTableFormat; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.Schema; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.Keyspace; -import org.apache.cassandra.db.compaction.DateTieredCompactionStrategy; import org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy; import org.apache.cassandra.io.sstable.Component; import org.apache.cassandra.io.sstable.format.SSTableReader; @@ -41,7 +41,7 @@ public static CassandraProxy getInstance() { private CassandraBackend() {} public List getKeyspaces() { - return Schema.instance.getNonLocalStrategyKeyspaces() + return Schema.instance.distributedKeyspaces() .stream() .map(ksmd -> ksmd.name).sorted().collect(Collectors.toList()); } @@ -92,7 +92,7 @@ public List getSSTableMetadata(String ksName, String cfName) { List metaData = new ArrayList<>(tables.size()); for (SSTableReader table : tables) { SSTableMetadata tableMetadata = new SSTableMetadata(); - File dataFile = new File(table.descriptor.filenameFor(Component.DATA)); + File dataFile = table.descriptor.fileFor(SSTableFormat.Components.DATA).toJavaIOFile(); tableMetadata.filename = dataFile.getName(); tableMetadata.ssTableId = table.descriptor.id; try { @@ -129,7 +129,6 @@ public ColumnFamilyProxy getColumnFamily(String ksName, String cfName, String sn Class compactionClass = metaData.params.compaction.klass(); return new ColumnFamilyBackend( metaData.partitionKeyType, - compactionClass.equals(DateTieredCompactionStrategy.class), compactionClass.equals(TimeWindowCompactionStrategy.class), cfStore, snapshotName, diff --git a/src/main/java/com/instaclustr/sstabletools/cassandra/ColumnFamilyBackend.java b/src/main/java/com/instaclustr/sstabletools/cassandra/ColumnFamilyBackend.java index 5c0729a..9d6c169 100644 --- a/src/main/java/com/instaclustr/sstabletools/cassandra/ColumnFamilyBackend.java +++ b/src/main/java/com/instaclustr/sstabletools/cassandra/ColumnFamilyBackend.java @@ -3,8 +3,16 @@ import com.instaclustr.sstabletools.*; import org.apache.cassandra.db.ColumnFamilyStore; import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.SerializationHeader; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.io.sstable.Component; +import org.apache.cassandra.io.sstable.format.SSTableFormat; +import org.apache.cassandra.io.sstable.format.big.BigFormat; +import org.apache.cassandra.io.sstable.format.big.BigTableReader; +import org.apache.cassandra.io.util.FileHandle; +import org.apache.cassandra.utils.FilterFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -12,21 +20,21 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; +import java.util.Set; /** * ColumnFamilyProxy using Cassandra 3.5 backend. */ public class ColumnFamilyBackend implements ColumnFamilyProxy { + + private static final Logger logger = LoggerFactory.getLogger(ColumnFamilyBackend.class); + /** * Key validator for column family. */ private AbstractType keyValidator; - /** - * Is column family using Date Tiered Compaction Strategy. - */ - private boolean isDTCS; - /** * Is column family using Time Window Compaction Strategy. */ @@ -53,13 +61,11 @@ public class ColumnFamilyBackend implements ColumnFamilyProxy { private Collection sstables; public ColumnFamilyBackend(AbstractType keyValidator, - boolean isDTCS, boolean isTWCS, ColumnFamilyStore cfStore, String snapshotName, Collection filter) throws IOException { this.keyValidator = keyValidator; - this.isDTCS = isDTCS; this.isTWCS = isTWCS; this.cfStore = cfStore; if (snapshotName != null) { @@ -74,7 +80,7 @@ public ColumnFamilyBackend(AbstractType keyValidator, if (filter != null) { List filteredSSTables = new ArrayList<>(sstables.size()); for (org.apache.cassandra.io.sstable.format.SSTableReader sstable : sstables) { - File dataFile = new File(sstable.descriptor.filenameFor(Component.DATA)); + File dataFile = sstable.descriptor.fileFor(SSTableFormat.Components.DATA).toJavaIOFile();; if (filter.contains(dataFile.getName())) { filteredSSTables.add(sstable); } @@ -88,7 +94,24 @@ public Collection getIndexReaders() { Collection readers = new ArrayList<>(sstables.size()); for (org.apache.cassandra.io.sstable.format.SSTableReader sstable : sstables) { try { - File dataFile = new File(sstable.descriptor.filenameFor(Component.DATA)); + Set components = sstable.descriptor.discoverComponents(); + + Optional maybeIndexComponent = components.stream().filter(c -> c.name.contains("Index")).findFirst(); + if (!maybeIndexComponent.isPresent()) { + continue; + } + + org.apache.cassandra.io.util.File indexFile = sstable.descriptor.fileFor(maybeIndexComponent.get()); + FileHandle indexHandle = new FileHandle.Builder(indexFile).complete(); + + BigTableReader reader = new BigTableReader.Builder(sstable.descriptor) + .setComponents(components) + .setFilter(FilterFactory.AlwaysPresent) + .setSerializationHeader(SerializationHeader.makeWithoutStats(cfStore.metadata())) + .setIndexFile(indexHandle) + .build(this.cfStore, false, false); + + File dataFile = sstable.descriptor.fileFor(SSTableFormat.Components.DATA).toJavaIOFile(); readers.add(new IndexReader( new SSTableStatistics( sstable.descriptor.id, @@ -97,12 +120,12 @@ public Collection getIndexReaders() { sstable.getMinTimestamp(), sstable.getMaxTimestamp(), sstable.getSSTableLevel()), - sstable.openIndexReader(), + reader.getIndexFile().createReader(), sstable.descriptor.version, sstable.getPartitioner() )); } catch (Throwable t) { - + logger.error("Error opening index readers", t); } } return readers; @@ -113,7 +136,7 @@ public Collection getDataReaders() { Collection readers = new ArrayList<>(sstables.size()); for (org.apache.cassandra.io.sstable.format.SSTableReader sstable : sstables) { try { - File dataFile = new File(sstable.descriptor.filenameFor(Component.DATA)); + File dataFile = sstable.descriptor.fileFor(SSTableFormat.Components.DATA).toJavaIOFile(); readers.add(new DataReader( new SSTableStatistics( sstable.descriptor.id, @@ -125,7 +148,9 @@ public Collection getDataReaders() { sstable.getScanner(), Util.NOW_SECONDS - sstable.metadata().params.gcGraceSeconds )); - } catch (Throwable t) {} + } catch (Throwable t) { + logger.error("Error while getting data readers", t); + } } return readers; } @@ -140,11 +165,6 @@ public String formatKey(DecoratedKey key) { return keyValidator.getString(key.getKey()); } - @Override - public boolean isDTCS() { - return isDTCS; - } - @Override public boolean isTWCS() { return isTWCS; diff --git a/src/main/java/com/instaclustr/sstabletools/cassandra/IndexReader.java b/src/main/java/com/instaclustr/sstabletools/cassandra/IndexReader.java index b88c502..3ebfd0d 100644 --- a/src/main/java/com/instaclustr/sstabletools/cassandra/IndexReader.java +++ b/src/main/java/com/instaclustr/sstabletools/cassandra/IndexReader.java @@ -67,7 +67,7 @@ public IndexReader(SSTableStatistics tableStats, RandomAccessReader reader, Vers * @throws IOException */ private void skipData() throws IOException { - int size = version.getVersion().compareTo("ma") >= 0 ? (int) reader.readUnsignedVInt() : reader.readInt(); + int size = version.version.compareTo("ma") >= 0 ? (int) reader.readUnsignedVInt() : reader.readInt(); if (size > 0) { reader.skipBytesFully(size); } @@ -81,14 +81,14 @@ public boolean next() { try { if (nextKey == null) { nextKey = ByteBufferUtil.readWithShortLength(reader); - nextPosition = version.getVersion().compareTo("ma") > 0 ? reader.readUnsignedVInt() : reader.readLong(); + nextPosition = version.version.compareTo("ma") > 0 ? reader.readUnsignedVInt() : reader.readLong(); skipData(); } partitionStats = new PartitionStatistics(partitioner.decorateKey(nextKey)); long position = nextPosition; if (!reader.isEOF()) { nextKey = ByteBufferUtil.readWithShortLength(reader); - nextPosition = version.getVersion().compareTo("ma") > 0 ? reader.readUnsignedVInt() : reader.readLong(); + nextPosition = version.version.compareTo("ma") > 0 ? reader.readUnsignedVInt() : reader.readLong(); skipData(); partitionStats.size = nextPosition - position; } else { diff --git a/src/main/java/com/instaclustr/sstabletools/cli/CLI.java b/src/main/java/com/instaclustr/sstabletools/cli/CLI.java index ac65d53..a89f593 100644 --- a/src/main/java/com/instaclustr/sstabletools/cli/CLI.java +++ b/src/main/java/com/instaclustr/sstabletools/cli/CLI.java @@ -1,7 +1,5 @@ package com.instaclustr.sstabletools.cli; -import java.io.PrintWriter; - import com.instaclustr.sstabletools.PurgeStatisticsCollector; import picocli.CommandLine; import picocli.CommandLine.Command; @@ -9,18 +7,18 @@ import picocli.CommandLine.Spec; @Command( - mixinStandardHelpOptions = true, - subcommands = { - ColumnFamilyStatisticsCollector.class, - PartitionSizeStatisticsCollector.class, - PurgeStatisticsCollector.class, - SSTableMetadataCollector.class, - SummaryCollector.class, - }, - versionProvider = CLI.class, - usageHelpWidth = 128 + mixinStandardHelpOptions = true, + subcommands = { + ColumnFamilyStatisticsCollector.class, + PartitionSizeStatisticsCollector.class, + PurgeStatisticsCollector.class, + SSTableMetadataCollector.class, + SummaryCollector.class, + }, + versionProvider = CLI.class, + usageHelpWidth = 128 ) -public class CLI extends JarManifestVersionProvider implements Runnable { +public class CLI extends CLIApplication implements Runnable { @Spec private CommandSpec spec; @@ -29,16 +27,12 @@ public static void main(String[] args) { main(args, true); } + public static void mainWithoutExit(String[] args) { + main(args, false); + } + public static void main(String[] args, boolean exit) { - int exitCode = new CommandLine(new CLI()) - .setErr(new PrintWriter(System.err)) - .setOut(new PrintWriter(System.err)) - .setColorScheme(new CommandLine.Help.ColorScheme.Builder().ansi(CommandLine.Help.Ansi.ON).build()) - .setExecutionExceptionHandler((ex, cmdLine, parseResult) -> { - ex.printStackTrace(); - return 1; - }) - .execute(args); + int exitCode = execute(new CommandLine(new CLI()), args); if (exit) { System.exit(exitCode); @@ -46,13 +40,13 @@ public static void main(String[] args, boolean exit) { } @Override - public String getImplementationTitle() { - return "ic-sstable-tools"; + public void run() { + throw new CommandLine.ParameterException(spec.commandLine(), "Missing required sub-command."); } @Override - public void run() { - throw new CommandLine.ParameterException(spec.commandLine(), "Missing required sub-command."); + public String title() { + return "ic-sstable-tools"; } } diff --git a/src/main/java/com/instaclustr/sstabletools/cli/CLIApplication.java b/src/main/java/com/instaclustr/sstabletools/cli/CLIApplication.java new file mode 100644 index 0000000..3265246 --- /dev/null +++ b/src/main/java/com/instaclustr/sstabletools/cli/CLIApplication.java @@ -0,0 +1,36 @@ +package com.instaclustr.sstabletools.cli; + +import picocli.CommandLine; + +import java.io.PrintWriter; +import java.util.concurrent.Callable; + +public abstract class CLIApplication implements CommandLine.IVersionProvider { + + public static int execute(final Runnable runnable, String... args) { + return execute(new CommandLine(runnable), args); + } + + public static int execute(final Callable callable, String... args) { + return execute(new CommandLine(callable), args); + } + + public static int execute(CommandLine commandLine, String... args) { + return commandLine + .setErr(new PrintWriter(System.err, true)) + .setOut(new PrintWriter(System.out, true)) + .setColorScheme(new CommandLine.Help.ColorScheme.Builder().ansi(CommandLine.Help.Ansi.ON).build()) + .setExecutionExceptionHandler((ex, cmdLine, parseResult) -> { + ex.printStackTrace(); + return 1; + }) + .execute(args); + } + + public abstract String title(); + + @Override + public String[] getVersion() throws Exception { + return VersionParser.parse(title()); + } +} diff --git a/src/main/java/com/instaclustr/sstabletools/cli/ColumnFamilyStatisticsCollector.java b/src/main/java/com/instaclustr/sstabletools/cli/ColumnFamilyStatisticsCollector.java index 6c09f59..1a70dfe 100644 --- a/src/main/java/com/instaclustr/sstabletools/cli/ColumnFamilyStatisticsCollector.java +++ b/src/main/java/com/instaclustr/sstabletools/cli/ColumnFamilyStatisticsCollector.java @@ -41,7 +41,7 @@ public class ColumnFamilyStatisticsCollector implements Runnable { @Option(names = {"-t"}, description = "Snapshot name", arity = "1") public String snapshotName; - @Option(names = {"-f"}, description = "Filter to sstables (comma separated", defaultValue = "") + @Option(names = {"-f"}, description = "Filter to sstables (comma separated)", defaultValue = "") public String filters; @Option(names = {"-b"}, description = "Batch mode", arity = "0") @@ -323,9 +323,6 @@ public void run() { List sstableStats = partitionReader.getSSTableStatistics(); Comparator comparator = SSTableStatistics.LIVENESS_COMPARATOR; - if (cfProxy.isDTCS()) { - comparator = SSTableStatistics.DTCS_COMPARATOR; - } if (cfProxy.isTWCS()) { comparator = SSTableStatistics.TWCS_COMPARATOR; } diff --git a/src/main/java/com/instaclustr/sstabletools/cli/JarManifestVersionProvider.java b/src/main/java/com/instaclustr/sstabletools/cli/JarManifestVersionProvider.java deleted file mode 100644 index a67ce7c..0000000 --- a/src/main/java/com/instaclustr/sstabletools/cli/JarManifestVersionProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.instaclustr.sstabletools.cli; - -import java.io.IOException; -import java.net.URL; -import java.util.Enumeration; -import java.util.Optional; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -import com.google.common.base.Joiner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import picocli.CommandLine; -import picocli.CommandLine.IVersionProvider; - -public abstract class JarManifestVersionProvider implements IVersionProvider { - - @Override - public String[] getVersion() throws IOException { - final Enumeration resources = CommandLine.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); - - Optional implementationVersion = Optional.empty(); - Optional buildTime = Optional.empty(); - Optional gitCommit = Optional.empty(); - - while (resources.hasMoreElements()) { - final URL url = resources.nextElement(); - - final Manifest manifest = new Manifest(url.openStream()); - final Attributes attributes = manifest.getMainAttributes(); - - if (isApplicableManifest(attributes)) { - implementationVersion = Optional.ofNullable(attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION)); - buildTime = Optional.ofNullable(attributes.getValue("Build-Time")); - gitCommit = Optional.ofNullable(attributes.getValue("Git-Commit")); - - break; - } - } - - return new String[]{ - String.format("%s %s", getImplementationTitle(), implementationVersion.orElse("development build")), - String.format("Build time: %s", buildTime.orElse("unknown")), - String.format("Git commit: %s", gitCommit.orElse("unknown")), - }; - } - - private boolean isApplicableManifest(Attributes attributes) { - return getImplementationTitle().equals(attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE)); - } - - public abstract String getImplementationTitle(); - - public static void logCommandVersionInformation(final CommandLine.Model.CommandSpec commandSpec) { - final Logger logger = LoggerFactory.getLogger(commandSpec.userObject().getClass()); - logger.info("{} version: {}", commandSpec.name(), Joiner.on(", ").join(commandSpec.version())); - } -} - diff --git a/src/main/java/com/instaclustr/sstabletools/cli/PartitionSizeStatisticsCollector.java b/src/main/java/com/instaclustr/sstabletools/cli/PartitionSizeStatisticsCollector.java index db6d4d0..8b9bb8b 100644 --- a/src/main/java/com/instaclustr/sstabletools/cli/PartitionSizeStatisticsCollector.java +++ b/src/main/java/com/instaclustr/sstabletools/cli/PartitionSizeStatisticsCollector.java @@ -38,7 +38,7 @@ public class PartitionSizeStatisticsCollector implements Runnable { @Option(names = {"-t"}, description = "Snapshot name", arity = "1") public String snapshotName; - @Option(names = {"-f"}, description = "Filter to sstables (comma separated", defaultValue = "") + @Option(names = {"-f"}, description = "Filter to sstables (comma separated)", defaultValue = "") public String filters; @Option(names = {"-b"}, description = "Batch mode", arity = "0") @@ -166,9 +166,6 @@ public void run() { ); List sstableStats = partitionReader.getSSTableStatistics(); Comparator comparator = SSTableStatistics.LIVENESS_COMPARATOR; - if (cfProxy.isDTCS()) { - comparator = SSTableStatistics.DTCS_COMPARATOR; - } if (cfProxy.isTWCS()) { comparator = SSTableStatistics.TWCS_COMPARATOR; } diff --git a/src/main/java/com/instaclustr/sstabletools/cli/SSTableMetadataCollector.java b/src/main/java/com/instaclustr/sstabletools/cli/SSTableMetadataCollector.java index 4b900f0..96e9820 100644 --- a/src/main/java/com/instaclustr/sstabletools/cli/SSTableMetadataCollector.java +++ b/src/main/java/com/instaclustr/sstabletools/cli/SSTableMetadataCollector.java @@ -10,7 +10,6 @@ import com.instaclustr.sstabletools.TableBuilder; import com.instaclustr.sstabletools.Util; import com.instaclustr.sstabletools.cassandra.CassandraBackend; -import org.apache.cassandra.db.compaction.DateTieredCompactionStrategy; import org.apache.cassandra.db.compaction.LeveledCompactionStrategy; import org.apache.cassandra.db.compaction.TimeWindowCompactionStrategy; import picocli.CommandLine.Command; @@ -60,9 +59,6 @@ public void run() { List metadataCollection = proxy.getSSTableMetadata(ksName, cfName); Class compactionClass = proxy.getCompactionClass(ksName, cfName); Comparator comparator = SSTableMetadata.GENERATION_COMPARATOR; - if (compactionClass.equals(DateTieredCompactionStrategy.class)) { - comparator = SSTableMetadata.DTCS_COMPARATOR; - } if (compactionClass.equals(TimeWindowCompactionStrategy.class)) { comparator = SSTableMetadata.TWCS_COMPARATOR; } diff --git a/src/main/java/com/instaclustr/sstabletools/cli/VersionParser.java b/src/main/java/com/instaclustr/sstabletools/cli/VersionParser.java new file mode 100644 index 0000000..d517107 --- /dev/null +++ b/src/main/java/com/instaclustr/sstabletools/cli/VersionParser.java @@ -0,0 +1,40 @@ +package com.instaclustr.sstabletools.cli; + +import picocli.CommandLine; + +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Optional; +import java.util.Properties; + +public class VersionParser { + public static String[] parse(String title) throws IOException + { + Enumeration resources = CommandLine.class.getClassLoader().getResources("git.properties"); + + Optional implementationVersion = Optional.empty(); + Optional buildTime = Optional.empty(); + Optional gitCommit = Optional.empty(); + + while (resources.hasMoreElements()) { + final URL url = resources.nextElement(); + + Properties properties = new Properties(); + properties.load(url.openStream()); + + if (properties.getProperty("git.build.time") != null) { + implementationVersion = Optional.ofNullable(properties.getProperty("git.build.version")); + buildTime = Optional.ofNullable(properties.getProperty("git.build.time")); + gitCommit = Optional.ofNullable(properties.getProperty("git.commit.id.full")); + } + } + + return new String[]{ + String.format("%s %s", title, implementationVersion.orElse("development build")), + String.format("Build time: %s", buildTime.orElse("unknown")), + String.format("Git commit: %s", gitCommit.orElse("unknown")), + }; + } + +}