Skip to content

Commit a22a9e0

Browse files
committed
Feat: support authentication
Signed-off-by: Evgeny Malygin <emalygin@bloomberg.net>
1 parent 7171c7c commit a22a9e0

21 files changed

Lines changed: 1015 additions & 31 deletions
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright 2026 Bloomberg Finance L.P.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.bloomberg.bmq;
17+
18+
import com.bloomberg.bmq.impl.infr.util.Argument;
19+
import java.util.Arrays;
20+
import javax.annotation.concurrent.Immutable;
21+
22+
/**
23+
* A value-semantic type representing authentication credentials for a broker session.
24+
*
25+
* <p>An {@code AuthnCredential} consists of an authentication mechanism name and optional
26+
* authentication data. It is used to configure credentials on a {@link SessionOptions} for
27+
* authenticating with the broker during session initiation.
28+
*
29+
* <H2>Thread Safety</H2>
30+
*
31+
* Once created, an {@code AuthnCredential} object is immutable and thread safe. A {@code Builder}
32+
* is provided for construction.
33+
*
34+
* <H2>Usage Example</H2>
35+
*
36+
* <pre>
37+
*
38+
* AuthnCredential credential = AuthnCredential.builder()
39+
* .setMechanism("OAUTH2")
40+
* .setData(tokenBytes)
41+
* .build();
42+
* </pre>
43+
*/
44+
@Immutable
45+
public final class AuthnCredential {
46+
47+
private final String mechanism;
48+
private final byte[] data;
49+
50+
private AuthnCredential() {
51+
mechanism = "";
52+
data = null;
53+
}
54+
55+
private AuthnCredential(Builder builder) {
56+
mechanism = builder.mechanism;
57+
data = builder.data != null ? Arrays.copyOf(builder.data, builder.data.length) : null;
58+
}
59+
60+
/**
61+
* Creates an {@code AuthnCredential} with default values (empty mechanism, no data).
62+
*
63+
* @return AuthnCredential credential with default values
64+
*/
65+
public static AuthnCredential createDefault() {
66+
return new AuthnCredential();
67+
}
68+
69+
/**
70+
* Returns a helper class object to create an immutable {@code AuthnCredential} with custom
71+
* settings.
72+
*
73+
* @return Builder a helper class object to create an immutable {@code AuthnCredential}
74+
*/
75+
public static Builder builder() {
76+
return new Builder();
77+
}
78+
79+
/**
80+
* Returns a helper class object initialized with values from the specified {@code credential}.
81+
*
82+
* @param credential specifies credential which initializes the builder
83+
* @return Builder a helper class object to create an immutable {@code AuthnCredential}
84+
*/
85+
public static Builder builder(AuthnCredential credential) {
86+
Argument.expectNonNull(credential, "credential");
87+
return new Builder(credential);
88+
}
89+
90+
/**
91+
* Returns the authentication mechanism.
92+
*
93+
* @return String authentication mechanism
94+
*/
95+
public String mechanism() {
96+
return mechanism;
97+
}
98+
99+
/**
100+
* Returns the authentication data, or null if not set.
101+
*
102+
* @return byte array of authentication data, or null
103+
*/
104+
public byte[] data() {
105+
return data != null ? Arrays.copyOf(data, data.length) : null;
106+
}
107+
108+
@Override
109+
public boolean equals(Object obj) {
110+
if (obj == this) return true;
111+
if (!(obj instanceof AuthnCredential)) return false;
112+
AuthnCredential other = (AuthnCredential) obj;
113+
return mechanism.equals(other.mechanism) && Arrays.equals(data, other.data);
114+
}
115+
116+
@Override
117+
public int hashCode() {
118+
int result = mechanism.hashCode();
119+
result = 31 * result + Arrays.hashCode(data);
120+
return result;
121+
}
122+
123+
@Override
124+
public String toString() {
125+
StringBuilder sb = new StringBuilder();
126+
sb.append("[ mechanism: ")
127+
.append(mechanism)
128+
.append(", data: ")
129+
.append(data != null ? "<" + data.length + " bytes>" : "null")
130+
.append(" ]");
131+
return sb.toString();
132+
}
133+
134+
/** A helper class to create an immutable {@code AuthnCredential} with custom settings. */
135+
public static class Builder {
136+
137+
private String mechanism;
138+
private byte[] data;
139+
140+
private Builder() {
141+
mechanism = "";
142+
data = null;
143+
}
144+
145+
private Builder(AuthnCredential credential) {
146+
mechanism = credential.mechanism;
147+
data =
148+
credential.data != null
149+
? Arrays.copyOf(credential.data, credential.data.length)
150+
: null;
151+
}
152+
153+
/**
154+
* Creates an {@code AuthnCredential} object based on this builder's properties.
155+
*
156+
* @return AuthnCredential immutable credential object
157+
*/
158+
public AuthnCredential build() {
159+
return new AuthnCredential(this);
160+
}
161+
162+
/**
163+
* Sets the authentication mechanism.
164+
*
165+
* @param value authentication mechanism
166+
* @return Builder this object
167+
* @throws NullPointerException if the specified value is null
168+
*/
169+
public Builder setMechanism(String value) {
170+
mechanism = Argument.expectNonNull(value, "mechanism");
171+
return this;
172+
}
173+
174+
/**
175+
* Sets the authentication data.
176+
*
177+
* @param value authentication data, may be null
178+
* @return Builder this object
179+
*/
180+
public Builder setData(byte[] value) {
181+
data = value != null ? Arrays.copyOf(value, value.length) : null;
182+
return this;
183+
}
184+
}
185+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2026 Bloomberg Finance L.P.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.bloomberg.bmq;
17+
18+
import com.bloomberg.bmq.impl.infr.util.Argument;
19+
20+
/**
21+
* Represents the result of an authentication credential callback, containing either a credential on
22+
* success or an error message on failure.
23+
*/
24+
public final class AuthnCredentialResult {
25+
26+
private final AuthnCredential credential;
27+
private final String error;
28+
29+
private AuthnCredentialResult(AuthnCredential credential, String error) {
30+
this.credential = credential;
31+
this.error = error;
32+
}
33+
34+
/**
35+
* Creates a successful result with the specified mechanism and data.
36+
*
37+
* @param mechanism the authentication mechanism
38+
* @param data the authentication data
39+
* @return a successful result
40+
* @throws NullPointerException if mechanism is null
41+
*/
42+
public static AuthnCredentialResult success(String mechanism, byte[] data) {
43+
return new AuthnCredentialResult(
44+
AuthnCredential.builder().setMechanism(mechanism).setData(data).build(), null);
45+
}
46+
47+
/**
48+
* Creates a failed result containing the specified error message.
49+
*
50+
* @param message the error message describing why credentials could not be obtained
51+
* @return a failed result
52+
* @throws NullPointerException if message is null
53+
*/
54+
public static AuthnCredentialResult error(String message) {
55+
Argument.expectNonNull(message, "message");
56+
return new AuthnCredentialResult(null, message);
57+
}
58+
59+
/**
60+
* Returns true if this result contains a credential.
61+
*
62+
* @return true on success, false on error
63+
*/
64+
public boolean isSuccess() {
65+
return credential != null;
66+
}
67+
68+
/**
69+
* Returns the credential, or null if this is an error result.
70+
*
71+
* @return the credential, or null
72+
*/
73+
public AuthnCredential credential() {
74+
return credential;
75+
}
76+
77+
/**
78+
* Returns the error message, or null if this is a success result.
79+
*
80+
* @return the error message, or null
81+
*/
82+
public String error() {
83+
return error;
84+
}
85+
}

bmq-sdk/src/main/java/com/bloomberg/bmq/SessionOptions.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,31 @@ public int highWaterMark() {
196196
}
197197
}
198198

199+
/**
200+
* Function type used to obtain authentication credentials for authenticating with the broker.
201+
*
202+
* <p>The function should return an {@link AuthnCredentialResult} containing either an {@link
203+
* AuthnCredential} on success, or an error message on failure.
204+
*
205+
* <p>If not set on {@code SessionOptions}, the session will not authenticate with the broker.
206+
*/
207+
@FunctionalInterface
208+
public interface AuthnCredentialCb {
209+
210+
/**
211+
* Returns authentication credentials or an error.
212+
*
213+
* @return an {@link AuthnCredentialResult} containing credentials on success, or an error
214+
* message on failure
215+
*/
216+
AuthnCredentialResult get();
217+
}
218+
219+
// Default to anonymous authentication so that all clients authenticate with
220+
// the broker seamlessly without requiring explicit configuration.
221+
private static final AuthnCredentialCb DEFAULT_AUTHN_CREDENTIAL_CB =
222+
() -> AuthnCredentialResult.success("ANONYMOUS", new byte[0]);
223+
199224
private static final URI DEFAULT_URI = URI.create("tcp://localhost:30114");
200225
private static final Duration DEFAULT_START_TIMEOUT = Duration.ofSeconds(65);
201226
private static final Duration DEFAULT_STOP_TIMEOUT = Duration.ofSeconds(30);
@@ -215,6 +240,8 @@ public int highWaterMark() {
215240

216241
private final HostHealthMonitor hostHealthMonitor;
217242

243+
private final AuthnCredentialCb authnCredentialCb;
244+
218245
private SessionOptions() {
219246
brokerUri = DEFAULT_URI;
220247
startTimeout = DEFAULT_START_TIMEOUT;
@@ -226,6 +253,7 @@ private SessionOptions() {
226253
configureQueueTimeout = QUEUE_OPERATION_TIMEOUT;
227254
closeQueueTimeout = QUEUE_OPERATION_TIMEOUT;
228255
hostHealthMonitor = null;
256+
authnCredentialCb = DEFAULT_AUTHN_CREDENTIAL_CB;
229257
}
230258

231259
private SessionOptions(Builder builder) {
@@ -239,6 +267,7 @@ private SessionOptions(Builder builder) {
239267
configureQueueTimeout = builder.configureQueueTimeout;
240268
closeQueueTimeout = builder.closeQueueTimeout;
241269
hostHealthMonitor = builder.hostHealthMonitor;
270+
authnCredentialCb = builder.authnCredentialCb;
242271
}
243272

244273
/**
@@ -383,6 +412,16 @@ public HostHealthMonitor hostHealthMonitor() {
383412
return hostHealthMonitor;
384413
}
385414

415+
/**
416+
* Returns the callback invoked to obtain authentication credentials for authenticating with the
417+
* broker. Default value is null, meaning the session will not authenticate.
418+
*
419+
* @return authentication credential callback, or null if not set
420+
*/
421+
public AuthnCredentialCb authnCredentialCb() {
422+
return authnCredentialCb;
423+
}
424+
386425
/** Helper class to create a {@code SesssionOptions} object with custom settings. */
387426
public static class Builder {
388427
private URI brokerUri;
@@ -397,6 +436,8 @@ public static class Builder {
397436
private Duration closeQueueTimeout;
398437

399438
private HostHealthMonitor hostHealthMonitor;
439+
private AuthnCredentialCb authnCredentialCb;
440+
400441
/**
401442
* Creates a {@code SesssionOptions} object based on this {@code Builder} properties.
402443
*
@@ -417,6 +458,7 @@ private Builder(SessionOptions options) {
417458
configureQueueTimeout = options.configureQueueTimeout;
418459
closeQueueTimeout = options.closeQueueTimeout;
419460
hostHealthMonitor = options.hostHealthMonitor;
461+
authnCredentialCb = options.authnCredentialCb;
420462
}
421463

422464
/**
@@ -558,5 +600,18 @@ public Builder setHostHealthMonitor(HostHealthMonitor value) {
558600
hostHealthMonitor = Argument.expectNonNull(value, "host health monitor");
559601
return this;
560602
}
603+
604+
/**
605+
* Sets the callback invoked to obtain authentication credentials for authenticating with
606+
* the broker. If not set, the session will not authenticate with the broker.
607+
*
608+
* @param value authentication credential callback
609+
* @return Builder this object
610+
* @throws NullPointerException if the specified value is null
611+
*/
612+
public Builder setAuthnCredentialCb(AuthnCredentialCb value) {
613+
authnCredentialCb = Argument.expectNonNull(value, "authnCredentialCb");
614+
return this;
615+
}
561616
}
562617
}

0 commit comments

Comments
 (0)