Skip to content

Commit 03a2a34

Browse files
committed
#946 Provide service managers with a way to customize request buffers
Backport of #945
1 parent 66048b9 commit 03a2a34

14 files changed

Lines changed: 312 additions & 22 deletions

src/docs/asciidoc/release_notes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ See also <<compat-legacy-auth>>.
5151
Fix was contributed by Artyom Abakumov.
5252
* Fixed: `NullPointerException` on authentication failure with non-existent user and single plugin in `AuthServer` (https://github.com/FirebirdSQL/jaybird/issues/940[#940])
5353
* Dependency update: updated `org.bouncycastle:bcprov-jdk18on` from 1.83 to 1.84 (used by `chacha64-plugin`) (https://github.com/FirebirdSQL/jaybird/issues/941[#941])
54+
* Improvement: Support customization of service requests in service managers (https://github.com/FirebirdSQL/jaybird/issues/946[#946])
5455

5556
=== Jaybird 6.0.5
5657

src/main/org/firebirdsql/management/FBServiceManager.java

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.firebirdsql.jaybird.props.PropertyConstants;
2828
import org.firebirdsql.jaybird.props.PropertyNames;
2929
import org.firebirdsql.jaybird.props.def.ConnectionProperty;
30+
import org.jspecify.annotations.Nullable;
3031

3132
import java.io.IOException;
3233
import java.io.OutputStream;
@@ -37,9 +38,9 @@
3738
import static org.firebirdsql.gds.ISCConstants.isc_info_svc_to_eof;
3839
import static org.firebirdsql.gds.ISCConstants.isc_info_truncated;
3940
import static org.firebirdsql.gds.ISCConstants.isc_infunk;
41+
import static org.firebirdsql.gds.VaxEncoding.iscVaxInteger2;
4042
import static org.firebirdsql.jaybird.fb.constants.SpbItems.isc_spb_dbname;
4143
import static org.firebirdsql.jaybird.fb.constants.SpbItems.isc_spb_options;
42-
import static org.firebirdsql.gds.VaxEncoding.iscVaxInteger2;
4344

4445
/**
4546
* An implementation of the basic Firebird Service API functionality.
@@ -53,6 +54,7 @@ public class FBServiceManager implements ServiceManager {
5354
private final FbDatabaseFactory dbFactory;
5455
private String database;
5556
private OutputStream logger;
57+
private @Nullable ServiceRequestCustomizer serviceRequestCustomizer;
5658

5759
public static final int BUFFER_SIZE = 1024; //1K
5860

@@ -310,6 +312,16 @@ public synchronized void setLogger(OutputStream logger) {
310312
this.logger = logger;
311313
}
312314

315+
@Override
316+
public void setServiceRequestCustomizer(@Nullable ServiceRequestCustomizer serviceRequestCustomizer) {
317+
this.serviceRequestCustomizer = serviceRequestCustomizer;
318+
}
319+
320+
@Override
321+
public @Nullable ServiceRequestCustomizer getServiceRequestCustomizer() {
322+
return serviceRequestCustomizer;
323+
}
324+
313325
public FbService attachServiceManager() throws SQLException {
314326
FbService fbService = dbFactory.serviceConnect(serviceProperties);
315327
fbService.attach();
@@ -403,13 +415,57 @@ public void queueService(FbService service) throws SQLException, IOException {
403415
*/
404416
protected final void executeServicesOperation(FbService service, ServiceRequestBuffer srb) throws SQLException {
405417
try {
406-
service.startServiceAction(srb);
418+
service.startServiceAction(customize(srb));
407419
queueService(service);
408420
} catch (IOException ioe) {
409421
throw new SQLException(ioe);
410422
}
411423
}
412424

425+
/**
426+
* Customizes the service request buffer, if a customizer was set.
427+
*
428+
* @param srb
429+
* service request buffer
430+
* @return {@code srb}, possibly modified
431+
* @throws SQLException
432+
* for exceptions thrown by the service request customizer
433+
* @see #setServiceRequestCustomizer(ServiceRequestCustomizer)
434+
* @since 6.0.5
435+
*/
436+
protected final ServiceRequestBuffer customize(ServiceRequestBuffer srb) throws SQLException {
437+
ServiceRequestCustomizer customizer = serviceRequestCustomizer;
438+
if (customizer != null) {
439+
var context = new ServiceRequestContext(determineOperation());
440+
try {
441+
customizer.customize(srb, context);
442+
} catch (RuntimeException e) {
443+
throw new SQLException("Service request terminated by ServiceRequestCustomizer", e);
444+
}
445+
}
446+
return srb;
447+
}
448+
449+
/**
450+
* Determines the service operation in this call chain.
451+
*
452+
* @return service operation
453+
*/
454+
private String determineOperation() {
455+
Class<?> serviceManagerClass = getClass();
456+
return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
457+
.walk(frames -> frames
458+
// Skip determineOperation and customize
459+
.skip(2)
460+
// Determine method calls within this service manager
461+
.takeWhile(f -> f.getDeclaringClass().isAssignableFrom(serviceManagerClass)
462+
&& ServiceManager.class.isAssignableFrom(f.getDeclaringClass()))
463+
// Obtain the last stack frame in the stream, which is the initiating operation
464+
.reduce((first, second) -> second)
465+
.map(StackWalker.StackFrame::getMethodName)
466+
.orElse("unknown"));
467+
}
468+
413469
protected ServiceRequestBuffer createRequestBuffer(FbService service, int operation, int options) {
414470
ServiceRequestBuffer srb = service.createServiceRequestBuffer();
415471
srb.addArgument(operation);

src/main/org/firebirdsql/management/FBStreamingBackupManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ protected void addBackupsToRestoreRequestBuffer(FbService service, ServiceReques
185185

186186
private void executeServiceBackupOperation(FbService service, ServiceRequestBuffer srb) throws SQLException {
187187
try {
188-
service.startServiceAction(srb);
188+
service.startServiceAction(customize(srb));
189189

190190
ServiceRequestBuffer infoSRB = service.createServiceRequestBuffer();
191191
infoSRB.addArgument(isc_info_svc_to_eof);
@@ -224,7 +224,7 @@ private void executeServiceRestoreOperation(FbService service, ServiceRequestBuf
224224
throw new SQLException("Verbose mode was requested but there is no logger provided.");
225225
}
226226
try {
227-
service.startServiceAction(srb);
227+
service.startServiceAction(customize(srb));
228228

229229
ServiceRequestBuffer infoSRB = service.createServiceRequestBuffer();
230230
infoSRB.addArgument(isc_info_svc_stdin);

src/main/org/firebirdsql/management/FBTraceManager.java

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Map;
3030

3131
import static org.firebirdsql.gds.ISCConstants.*;
32+
import static org.firebirdsql.jaybird.util.StringUtils.isNullOrEmpty;
3233

3334
/**
3435
* Implements the Trace/Audit API available new in Firebird 2.5
@@ -148,7 +149,7 @@ private ServiceRequestBuffer getTraceSPB(FbService service, int action, String t
148149
* Firebird installation
149150
*/
150151
public void startTraceSession(String traceSessionName, String configuration) throws SQLException {
151-
if (configuration == null || configuration.equals("")) {
152+
if (isNullOrEmpty(configuration)) {
152153
throw new SQLException("No configuration provided");
153154
}
154155
if (traceSessionName == null) {
@@ -179,10 +180,7 @@ public void startTraceSession(String traceSessionName, String configuration) thr
179180
*/
180181
public void stopTraceSession(int traceSessionId) throws SQLException {
181182
try (FbService service = attachServiceManager()) {
182-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_stop, traceSessionId));
183-
queueService(service);
184-
} catch (IOException ioe) {
185-
throw new SQLException(ioe);
183+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_stop, traceSessionId));
186184
}
187185
}
188186

@@ -194,10 +192,7 @@ public void stopTraceSession(int traceSessionId) throws SQLException {
194192
*/
195193
public void suspendTraceSession(int traceSessionId) throws SQLException {
196194
try (FbService service = attachServiceManager()) {
197-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_suspend, traceSessionId));
198-
queueService(service);
199-
} catch (IOException ioe) {
200-
throw new SQLException(ioe);
195+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_suspend, traceSessionId));
201196
}
202197
}
203198

@@ -209,10 +204,7 @@ public void suspendTraceSession(int traceSessionId) throws SQLException {
209204
*/
210205
public void resumeTraceSession(int traceSessionId) throws SQLException {
211206
try (FbService service = attachServiceManager()) {
212-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_resume, traceSessionId));
213-
queueService(service);
214-
} catch (IOException ioe) {
215-
throw new SQLException(ioe);
207+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_resume, traceSessionId));
216208
}
217209
}
218210

@@ -221,10 +213,7 @@ public void resumeTraceSession(int traceSessionId) throws SQLException {
221213
*/
222214
public void listTraceSessions() throws SQLException {
223215
try (FbService service = attachServiceManager()) {
224-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_list));
225-
queueService(service);
226-
} catch (IOException ioe) {
227-
throw new SQLException(ioe);
216+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_list));
228217
}
229218
}
230219

src/main/org/firebirdsql/management/ServiceManager.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.firebirdsql.gds.ng.WireCrypt;
2929
import org.firebirdsql.jaybird.props.AttachmentProperties;
3030
import org.firebirdsql.jaybird.props.ServiceConnectionProperties;
31+
import org.jspecify.annotations.Nullable;
3132

3233
import java.io.OutputStream;
3334
import java.sql.SQLException;
@@ -132,6 +133,32 @@ public interface ServiceManager extends ServiceConnectionProperties {
132133
*/
133134
void setLogger(OutputStream logger);
134135

136+
/**
137+
* Sets a service request customizer on this service manager. This replaces any previously set customizer.
138+
* <p>
139+
* The customizer is called just before the request is sent to the server, allowing users to modify the request.
140+
* </p>
141+
* <p>
142+
* If you set a customizer to access a feature not implemented by Jaybird, please consider creating an improvement
143+
* ticket on <a href="https://github.com/FirebirdSQL/jaybird/issues">the Jaybird GitHub repository</a> as well.
144+
* </p>
145+
*
146+
* @param customizer
147+
* service request customizer, {@code null} to remove a previous customizer
148+
* @see #getServiceRequestCustomizer()
149+
* @see ServiceRequestCustomizer
150+
* @since 6.0.5
151+
*/
152+
void setServiceRequestCustomizer(@Nullable ServiceRequestCustomizer customizer);
153+
154+
/**
155+
* @return service request customizer, {@code null} if none set
156+
* @see #setServiceRequestCustomizer(ServiceRequestCustomizer)
157+
* @since 6.0.5
158+
*/
159+
@Nullable ServiceRequestCustomizer getServiceRequestCustomizer();
160+
161+
135162
/**
136163
* Obtains the server version through a service call.
137164
*
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Public Firebird Java API.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are met:
6+
* 1. Redistributions of source code must retain the above copyright notice,
7+
* this list of conditions and the following disclaimer.
8+
* 2. Redistributions in binary form must reproduce the above copyright
9+
* notice, this list of conditions and the following disclaimer in the
10+
* documentation and/or other materials provided with the distribution.
11+
* 3. The name of the author may not be used to endorse or promote products
12+
* derived from this software without specific prior written permission.
13+
*
14+
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16+
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17+
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
20+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22+
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23+
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
package org.firebirdsql.management;
26+
27+
/**
28+
* Context information about a service request for {@link ServiceRequestCustomizer}.
29+
*
30+
* @param operation
31+
* name of the operation (the initiating service method name)
32+
* @since 6.0.5
33+
*/
34+
public record ServiceRequestContext(String operation) {
35+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Public Firebird Java API.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are met:
6+
* 1. Redistributions of source code must retain the above copyright notice,
7+
* this list of conditions and the following disclaimer.
8+
* 2. Redistributions in binary form must reproduce the above copyright
9+
* notice, this list of conditions and the following disclaimer in the
10+
* documentation and/or other materials provided with the distribution.
11+
* 3. The name of the author may not be used to endorse or promote products
12+
* derived from this software without specific prior written permission.
13+
*
14+
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
15+
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16+
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
17+
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
20+
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
21+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
22+
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23+
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
package org.firebirdsql.management;
26+
27+
import org.firebirdsql.gds.ServiceRequestBuffer;
28+
29+
/**
30+
* Functional interface for modifying service requests.
31+
* <p>
32+
* This can be used to modify service requests before they are sent to the server, for example to access features not
33+
* currently supported by Jaybird.
34+
* </p>
35+
* <p>
36+
* If you implement this interface to access a feature not implemented by Jaybird, please consider creating an
37+
* improvement ticket on <a href="https://github.com/FirebirdSQL/jaybird/issues">the Jaybird GitHub repository</a> as
38+
* well.
39+
* </p>
40+
*
41+
* @see ServiceManager#setServiceRequestCustomizer(ServiceRequestCustomizer)
42+
* @since 6.0.5
43+
*/
44+
@FunctionalInterface
45+
public interface ServiceRequestCustomizer {
46+
47+
/**
48+
* Provides access to, and allows customization of, a service request.
49+
* <p>
50+
* Called by the service manager just before the request is sent to the server. Exceptions thrown by the customizer
51+
* terminate the service request before it's sent to the server.
52+
* </p>
53+
* <p>
54+
* Incorrect use (e.g. adding wrong arguments or values, removing or replacing arguments, etc.) may result in
55+
* errors on Firebird or in Jaybird.
56+
* </p>
57+
*
58+
* @param serviceRequest
59+
* service request buffer populated by the service manager, to be modified by this customizer
60+
* @param requestContext
61+
* service request context information
62+
*/
63+
void customize(ServiceRequestBuffer serviceRequest, ServiceRequestContext requestContext);
64+
65+
}

src/test/org/firebirdsql/management/FBBackupManagerTest.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class FBBackupManagerTest {
6868
final UsesDatabaseExtension.UsesDatabaseForEach usesDatabase = UsesDatabaseExtension.noDatabase();
6969

7070
private BackupManager backupManager;
71+
private final GetServiceRequestContext getServiceRequestContext = new GetServiceRequestContext();
7172
@TempDir
7273
Path tempFolder;
7374

@@ -89,6 +90,7 @@ void setUp() {
8990
backupManager.setParallelWorkers(2);
9091
backupManager.setLogger(System.out);
9192
backupManager.setVerbose(true);
93+
backupManager.setServiceRequestCustomizer(getServiceRequestContext);
9294
}
9395

9496
private String getBackupPath() {
@@ -108,18 +110,32 @@ void testBackup() throws Exception {
108110
usesDatabase.createDefaultDatabase();
109111
backupManager.backupDatabase();
110112

111-
final Path restorePath = tempFolder.resolve("testrestore.fdb");
113+
getServiceRequestContext.assertLastOperation("backupDatabase");
112114

113115
backupManager.clearRestorePaths();
116+
final Path restorePath = tempFolder.resolve("testrestore.fdb");
114117
usesDatabase.addDatabase(restorePath.toString());
115118
backupManager.setDatabase(restorePath.toString());
116119
backupManager.restoreDatabase();
117120

121+
getServiceRequestContext.assertLastOperation("restoreDatabase");
118122
try (var c = DriverManager.getConnection(getUrl(restorePath), getDefaultPropertiesForConnection())) {
119123
assertTrue(c.isValid(0));
120124
}
121125
}
122126

127+
@Test
128+
void testBackupMetadata() throws Exception {
129+
usesDatabase.createDefaultDatabase();
130+
backupManager.clearBackupPaths();
131+
final Path backupPath = tempFolder.resolve("testmetadatabackup.fbk");
132+
backupManager.setBackupPath(backupPath.toString());
133+
backupManager.backupMetadata();
134+
135+
assertTrue(Files.isRegularFile(backupPath), "Expected file to exist");
136+
getServiceRequestContext.assertLastOperation("backupMetadata");
137+
}
138+
123139
@Test
124140
void testSetBadBufferCount() {
125141
assertThrows(IllegalArgumentException.class, () -> backupManager.setRestorePageBufferCount(-1),

0 commit comments

Comments
 (0)