Skip to content

Commit

Permalink
feat: Add app string to transactional metadata and unify with BoltA…
Browse files Browse the repository at this point in the history
…gent.

This takes also `ApplicationName` from the client info if available into the `app` string.

Closes #523.

Signed-off-by: Michael Simons <[email protected]>
  • Loading branch information
michael-simons committed Jan 9, 2025
1 parent f1baf87 commit 791e4f2
Show file tree
Hide file tree
Showing 17 changed files with 233 additions and 144 deletions.
1 change: 1 addition & 0 deletions etc/checkstyle/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
<property
name="excludes"
value="
com.github.stefanbirkner.systemlambda.SystemLambda.*,
com.tngtech.archunit.base.DescribedPredicate.*,
com.tngtech.archunit.core.domain.JavaClass.Predicates.*,
com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*
Expand Down
5 changes: 5 additions & 0 deletions neo4j-jdbc-it/neo4j-jdbc-it-stub/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
</properties>

<dependencies>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.neo4j.jdbc.it.stub.server.IntegrationTestBase;
import org.neo4j.jdbc.it.stub.server.StubScript;

import static com.github.stefanbirkner.systemlambda.SystemLambda.restoreSystemProperties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

Expand Down Expand Up @@ -284,24 +285,34 @@ void shouldTimeoutOnIsValidOnNetworkTimeoutWithTransacton() throws SQLException
false, false
""")
@StubScript(path = "tx_meta.script")
void shouldSendMetadata(boolean autoCommit, boolean onConnection) throws SQLException {
try (var connection = getConnection(); var statement = connection.createStatement()) {
connection.setAutoCommit(autoCommit);

Neo4jMetadataWriter metadataWriter = onConnection ? connection.unwrap(Neo4jMetadataWriter.class)
: statement.unwrap(Neo4jMetadataWriter.class);
metadataWriter.withMetadata(Map.of("akey", "aval"));

statement.unwrap(Neo4jMetadataWriter.class).withMetadata(Map.of("akey2", "aval2"));

var result = statement.executeQuery("RETURN 1 as n");
while (result.next()) {
assertThat(result.getInt(1)).isEqualTo(1);
void shouldSendMetadata(boolean autoCommit, boolean onConnection) throws Exception {
restoreSystemProperties(() -> {

System.setProperty("java.version", "1.4");
System.setProperty("java.vm.vendor", "ms");
System.setProperty("java.vm.name", "fake");
System.clearProperty("java.vm.version");
System.setProperty("neo4j.jdbc.version", "xxx");

try (var connection = getConnection(); var statement = connection.createStatement()) {
connection.setClientInfo("ApplicationName", "StubServerTest");
connection.setAutoCommit(autoCommit);

Neo4jMetadataWriter metadataWriter = onConnection ? connection.unwrap(Neo4jMetadataWriter.class)
: statement.unwrap(Neo4jMetadataWriter.class);
metadataWriter.withMetadata(Map.of("akey", "aval"));

statement.unwrap(Neo4jMetadataWriter.class).withMetadata(Map.of("akey2", "aval2"));

var result = statement.executeQuery("RETURN 1 as n");
while (result.next()) {
assertThat(result.getInt(1)).isEqualTo(1);
}
if (!autoCommit) {
connection.commit();
}
}
if (!autoCommit) {
connection.commit();
}
}
});

verifyStubServer();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A: HELLO {"{}": "*"}
A: LOGON {"scheme": "basic", "principal": "neo4j", "credentials": "password"}
*: RESET
C: BEGIN {"db": "neo4j", "[tx_type]": "*", "tx_metadata": {"akey": "aval", "akey2": "aval2"}}
C: BEGIN {"db": "neo4j", "[tx_type]": "*", "tx_metadata": {"app": "StubServerTest Java/1.4 (ms fake -) neo4j-jdbc/xxx", "akey": "aval", "akey2": "aval2"}}
S: SUCCESS {}
C: RUN "RETURN 1 as n" {} {}
S: SUCCESS {"fields": ["n"]}
Expand Down
5 changes: 5 additions & 0 deletions neo4j-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
<groupId>org.neo4j</groupId>
<artifactId>neo4j-jdbc-translator-spi</artifactId>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-lambda</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit</artifactId>
Expand Down
41 changes: 35 additions & 6 deletions neo4j-jdbc/src/main/java/org/neo4j/jdbc/ConnectionImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CompletionStage;
Expand All @@ -53,6 +54,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
Expand Down Expand Up @@ -175,17 +177,14 @@ UnaryOperator<String> getTranslator(boolean force, Consumer<SQLWarning> warningC
return sqlTranslator;
}

@SuppressWarnings("MagicConstant") // This is the whole purpose here to encapsulate
// those
@SuppressWarnings("MagicConstant") // On purpose
@Override
public Statement createStatement() throws SQLException {
return this.createStatement(ResultSetImpl.SUPPORTED_TYPE, ResultSetImpl.SUPPORTED_CONCURRENCY,
ResultSetImpl.SUPPORTED_HOLDABILITY);
}

@SuppressWarnings({ "MagicConstant", "SqlSourceToSinkFlow" }) // This is the whole
// purpose here to
// encapsulate those
@SuppressWarnings({ "MagicConstant", "SqlSourceToSinkFlow" }) // On purpose
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return prepareStatement(sql, ResultSetImpl.SUPPORTED_TYPE, ResultSetImpl.SUPPORTED_CONCURRENCY,
Expand Down Expand Up @@ -738,9 +737,12 @@ Neo4jTransaction getTransaction(Map<String, Object> additionalTransactionMetadat
&& Neo4jTransaction.State.FAILED.equals(this.transaction.getState()))
? this.boltConnection.reset(false) : null;
Map<String, Object> combinedTransactionMetadata = new HashMap<>(
this.transactionMetadata.size() + additionalTransactionMetadata.size());
this.transactionMetadata.size() + additionalTransactionMetadata.size() + 1);
combinedTransactionMetadata.putAll(this.transactionMetadata);
combinedTransactionMetadata.putAll(additionalTransactionMetadata);
if (!combinedTransactionMetadata.containsKey("app")) {
combinedTransactionMetadata.put("app", this.getApp());
}
this.transaction = new DefaultTransactionImpl(this.boltConnection, this.bookmarkManager,
combinedTransactionMetadata, this::handleFatalException, resetStage, this.autoCommit,
getAccessMode(), null);
Expand Down Expand Up @@ -813,6 +815,33 @@ public Neo4jConnection withMetadata(Map<String, Object> metadata) {
return this;
}

/**
* A string suitable to be used as {@code app} value inside Neo4j transactional
* metadata.
* @return a string suitable to be used as {@code app} value inside Neo4j
* transactional metadata
*/
String getApp() throws SQLException {
var applicationName = getClientInfo("ApplicationName");
return String.format("%sJava/%s (%s %s %s) neo4j-jdbc/%s",
(applicationName == null || applicationName.isBlank()) ? "" : applicationName.trim() + " ",
Optional.ofNullable(System.getProperty("java.version"))
.filter(Predicate.not(String::isBlank))
.orElse("-"),
Optional.ofNullable(System.getProperty("java.vm.vendor"))
.filter(Predicate.not(String::isBlank))
.orElse("-"),
Optional.ofNullable(System.getProperty("java.vm.name"))
.filter(Predicate.not(String::isBlank))
.orElse("-"),
Optional.ofNullable(System.getProperty("java.vm.version"))
.filter(Predicate.not(String::isBlank))
.orElse("-"),
Optional.ofNullable(System.getProperty("neo4j.jdbc.version"))
.filter(Predicate.not(String::isBlank))
.orElseGet(ProductVersion::getValue));
}

static class TranslatorChain implements UnaryOperator<String> {

private final List<Translator> translators;
Expand Down
4 changes: 2 additions & 2 deletions neo4j-jdbc/src/main/java/org/neo4j/jdbc/Neo4jDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import org.neo4j.jdbc.internal.bolt.AuthTokens;
import org.neo4j.jdbc.internal.bolt.BoltAgentUtil;
import org.neo4j.jdbc.internal.bolt.BoltAgent;
import org.neo4j.jdbc.internal.bolt.BoltConnectionProvider;
import org.neo4j.jdbc.internal.bolt.BoltConnectionProviders;
import org.neo4j.jdbc.internal.bolt.BoltServerAddress;
Expand Down Expand Up @@ -333,7 +333,7 @@ public Connection connect(String url, Properties info) throws SQLException {
case KERBEROS -> AuthTokens.kerberos(password);
};

var boltAgent = BoltAgentUtil.boltAgent();
var boltAgent = BoltAgent.of(ProductVersion.getValue());
var userAgent = driverConfig.agent;
var connectTimeoutMillis = driverConfig.timeout;

Expand Down
6 changes: 4 additions & 2 deletions neo4j-jdbc/src/main/java/org/neo4j/jdbc/ProductVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ static int getMinorVersion() {
private static int getVersion(int idx) {

var value = getValue();
if ("unknown".equalsIgnoreCase(value)) {
if ("dev".equalsIgnoreCase(value)) {
throw new IllegalArgumentException("Unsupported or unknown version '%s'".formatted(value));
}
var part = value.split("\\.")[idx];
Expand All @@ -73,6 +73,8 @@ private static int getVersion(int idx) {

private static String getVersionImpl() {
try {
// Using Neo4jDriver.class.getPackage().getImplementationVersion()
// doesn't work on the module path
Enumeration<URL> resources = Neo4jDriver.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
Expand All @@ -87,7 +89,7 @@ private static String getVersionImpl() {
throw new UncheckedIOException("Unable to read from neo4j-jdbc manifest.", ex);
}

return "unknown";
return "dev";
}

private static boolean isApplicableManifest(Manifest manifest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,46 @@
*/
package org.neo4j.jdbc.internal.bolt;

import java.util.Optional;

public record BoltAgent(String product, String platform, String language, String languageDetails) {

public static BoltAgent of(String driverVersion) {
var platformBuilder = new StringBuilder();
getProperty("os.name").ifPresent(value -> append(value, platformBuilder));
getProperty("os.version").ifPresent(value -> append(value, platformBuilder));
getProperty("os.arch").ifPresent(value -> append(value, platformBuilder));

var language = getProperty("java.version").map(version -> "Java/" + version);

var languageDetailsBuilder = new StringBuilder();
getProperty("java.vm.vendor").ifPresent(value -> append(value, languageDetailsBuilder));
getProperty("java.vm.name").ifPresent(value -> append(value, languageDetailsBuilder));
getProperty("java.vm.version").ifPresent(value -> append(value, languageDetailsBuilder));

return new BoltAgent(String.format("neo4j-jdbc/%s", driverVersion),
platformBuilder.isEmpty() ? null : platformBuilder.toString(), language.orElse(null),
languageDetailsBuilder.isEmpty() ? null : languageDetailsBuilder.toString());
}

private static Optional<String> getProperty(String key) {
try {
var value = System.getProperty(key);
if (value != null) {
value = value.trim();
}
return (value != null && !value.isEmpty()) ? Optional.of(value) : Optional.empty();
}
catch (SecurityException exception) {
return Optional.empty();
}
}

private static void append(String value, StringBuilder builder) {
if (value != null && !value.isEmpty()) {
var separator = builder.isEmpty() ? "" : "; ";
builder.append(separator).append(value);
}
}

}

This file was deleted.

Loading

0 comments on commit 791e4f2

Please sign in to comment.