Skip to content

Commit 295501c

Browse files
committed
#947 Provide service managers with a way to customize request buffers
Backport of #945
1 parent 2215b9a commit 295501c

14 files changed

Lines changed: 346 additions & 28 deletions

src/docs/asciidoc/release_notes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ This change was backported from Jaybird 6.0.6.
5656
* Fixed: Off-by-one error in default value for `maxInlineBlobSize` (https://github.com/FirebirdSQL/jaybird/issues/939[#939])
5757
+
5858
Fix was contributed by Artyom Abakumov, and backport from Jaybird 6.0.6.
59+
* Improvement: Support customization of service requests in service managers (https://github.com/FirebirdSQL/jaybird/issues/947[#947])
5960
6061
[#jaybird-5-0-12-changelog]
6162
=== Jaybird 5.0.12

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

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919
package org.firebirdsql.management;
2020

21+
import jdk.jfr.StackTrace;
2122
import org.firebirdsql.gds.ServiceRequestBuffer;
2223
import org.firebirdsql.gds.impl.DbAttachInfo;
2324
import org.firebirdsql.gds.impl.GDSFactory;
@@ -31,14 +32,15 @@
3132
import java.io.IOException;
3233
import java.io.OutputStream;
3334
import java.sql.SQLException;
35+
import java.util.Arrays;
3436
import java.util.Map;
3537

3638
import static org.firebirdsql.gds.ISCConstants.isc_info_end;
3739
import static org.firebirdsql.gds.ISCConstants.isc_info_svc_to_eof;
3840
import static org.firebirdsql.gds.ISCConstants.isc_info_truncated;
41+
import static org.firebirdsql.gds.VaxEncoding.iscVaxInteger2;
3942
import static org.firebirdsql.jaybird.fb.constants.SpbItems.isc_spb_dbname;
4043
import static org.firebirdsql.jaybird.fb.constants.SpbItems.isc_spb_options;
41-
import static org.firebirdsql.gds.VaxEncoding.iscVaxInteger2;
4244

4345
/**
4446
* An implementation of the basic Firebird Service API functionality.
@@ -52,6 +54,7 @@ public class FBServiceManager implements ServiceManager {
5254
private final FbDatabaseFactory dbFactory;
5355
private String database;
5456
private OutputStream logger;
57+
private ServiceRequestCustomizer serviceRequestCustomizer;
5558

5659
public final static int BUFFER_SIZE = 1024; //1K
5760

@@ -305,6 +308,16 @@ public synchronized void setLogger(OutputStream logger) {
305308
this.logger = logger;
306309
}
307310

311+
@Override
312+
public void setServiceRequestCustomizer(ServiceRequestCustomizer serviceRequestCustomizer) {
313+
this.serviceRequestCustomizer = serviceRequestCustomizer;
314+
}
315+
316+
@Override
317+
public ServiceRequestCustomizer getServiceRequestCustomizer() {
318+
return serviceRequestCustomizer;
319+
}
320+
308321
public FbService attachServiceManager() throws SQLException {
309322
FbService fbService = dbFactory.serviceConnect(serviceProperties);
310323
fbService.attach();
@@ -410,22 +423,81 @@ public void queueService(FbService service) throws SQLException, IOException {
410423
@Deprecated
411424
protected void executeServicesOperation(ServiceRequestBuffer srb) throws SQLException {
412425
try (FbService service = attachServiceManager()) {
413-
service.startServiceAction(srb);
414-
queueService(service);
415-
} catch (IOException ioe) {
416-
throw new SQLException(ioe);
426+
executeServicesOperation(service, srb);
417427
}
418428
}
419429

420430
protected final void executeServicesOperation(FbService service, ServiceRequestBuffer srb) throws SQLException {
421431
try {
422-
service.startServiceAction(srb);
432+
service.startServiceAction(customize(srb));
423433
queueService(service);
424434
} catch (IOException ioe) {
425435
throw new SQLException(ioe);
426436
}
427437
}
428438

439+
/**
440+
* Customizes the service request buffer, if a customizer was set.
441+
*
442+
* @param srb
443+
* service request buffer
444+
* @return {@code srb}, possibly modified
445+
* @throws SQLException
446+
* for exceptions thrown by the service request customizer
447+
* @see #setServiceRequestCustomizer(ServiceRequestCustomizer)
448+
* @since 7
449+
*/
450+
protected final ServiceRequestBuffer customize(ServiceRequestBuffer srb) throws SQLException {
451+
ServiceRequestCustomizer customizer = serviceRequestCustomizer;
452+
if (customizer != null) {
453+
ServiceRequestContext context = new ServiceRequestContext(determineOperation());
454+
try {
455+
customizer.customize(srb, context);
456+
} catch (RuntimeException e) {
457+
throw new SQLException("Service request terminated by ServiceRequestCustomizer", e);
458+
}
459+
}
460+
return srb;
461+
}
462+
463+
/**
464+
* Determines the service operation in this call chain.
465+
*
466+
* @return service operation
467+
*/
468+
private String determineOperation() {
469+
class StackFrame {
470+
final Class<?> clazz;
471+
final String methodName;
472+
473+
StackFrame(String className, String methodName) {
474+
Class<?> tempClass;
475+
try {
476+
tempClass = Class.forName(className);
477+
} catch (ClassNotFoundException e) {
478+
// Shouldn't happen, and if it does, it is likely a class we're not interested in
479+
tempClass = Object.class;
480+
}
481+
clazz = tempClass;
482+
this.methodName = methodName;
483+
}
484+
}
485+
Class<?> serviceManagerClass = getClass();
486+
StackFrame lastFrame = null;
487+
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
488+
// Start at 3 to skip getStackTrace, determineOperation and customize
489+
for (int idx = 3; idx < stackTrace.length; idx++) {
490+
StackTraceElement currentElement = stackTrace[idx];
491+
StackFrame currentFrame = new StackFrame(currentElement.getClassName(), currentElement.getMethodName());
492+
if (!currentFrame.clazz.isAssignableFrom(serviceManagerClass)
493+
|| !ServiceManager.class.isAssignableFrom(currentFrame.clazz)) {
494+
break;
495+
}
496+
lastFrame = currentFrame;
497+
}
498+
return lastFrame != null ? lastFrame.methodName : "unknown";
499+
}
500+
429501
protected ServiceRequestBuffer createRequestBuffer(FbService service, int operation, int options) {
430502
ServiceRequestBuffer srb = service.createServiceRequestBuffer();
431503
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);
@@ -220,7 +220,7 @@ private void executeServiceBackupOperation(FbService service, ServiceRequestBuff
220220

221221
private void executeServiceRestoreOperation(FbService service, ServiceRequestBuffer srb) throws SQLException {
222222
try {
223-
service.startServiceAction(srb);
223+
service.startServiceAction(customize(srb));
224224

225225
OutputStream currentLogger = getLogger();
226226
ServiceRequestBuffer infoSRB = service.createServiceRequestBuffer();

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.util.StringUtils.isNullOrEmpty;
3233

3334
/**
3435
* Implements the Trace/Audit API available new in Firebird 2.5
@@ -150,7 +151,7 @@ private ServiceRequestBuffer getTraceSPB(FbService service, int action, String t
150151
* Firebird installation
151152
*/
152153
public void startTraceSession(String traceSessionName, String configuration) throws SQLException {
153-
if (configuration == null || configuration.equals("")) {
154+
if (isNullOrEmpty(configuration)) {
154155
throw new SQLException("No configuration provided");
155156
}
156157
if (traceSessionName == null) {
@@ -181,10 +182,7 @@ public void startTraceSession(String traceSessionName, String configuration) thr
181182
*/
182183
public void stopTraceSession(int traceSessionId) throws SQLException {
183184
try (FbService service = attachServiceManager()) {
184-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_stop, traceSessionId));
185-
queueService(service);
186-
} catch (IOException ioe) {
187-
throw new SQLException(ioe);
185+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_stop, traceSessionId));
188186
}
189187
}
190188

@@ -196,10 +194,7 @@ public void stopTraceSession(int traceSessionId) throws SQLException {
196194
*/
197195
public void suspendTraceSession(int traceSessionId) throws SQLException {
198196
try (FbService service = attachServiceManager()) {
199-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_suspend, traceSessionId));
200-
queueService(service);
201-
} catch (IOException ioe) {
202-
throw new SQLException(ioe);
197+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_suspend, traceSessionId));
203198
}
204199
}
205200

@@ -211,10 +206,7 @@ public void suspendTraceSession(int traceSessionId) throws SQLException {
211206
*/
212207
public void resumeTraceSession(int traceSessionId) throws SQLException {
213208
try (FbService service = attachServiceManager()) {
214-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_resume, traceSessionId));
215-
queueService(service);
216-
} catch (IOException ioe) {
217-
throw new SQLException(ioe);
209+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_resume, traceSessionId));
218210
}
219211
}
220212

@@ -223,10 +215,7 @@ public void resumeTraceSession(int traceSessionId) throws SQLException {
223215
*/
224216
public void listTraceSessions() throws SQLException {
225217
try (FbService service = attachServiceManager()) {
226-
service.startServiceAction(getTraceSPB(service, isc_action_svc_trace_list));
227-
queueService(service);
228-
} catch (IOException ioe) {
229-
throw new SQLException(ioe);
218+
executeServicesOperation(service, getTraceSPB(service, isc_action_svc_trace_list));
230219
}
231220
}
232221

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,31 @@ public interface ServiceManager extends ServiceConnectionProperties {
132132
*/
133133
void setLogger(OutputStream logger);
134134

135+
/**
136+
* Sets a service request customizer on this service manager. This replaces any previously set customizer.
137+
* <p>
138+
* The customizer is called just before the request is sent to the server, allowing users to modify the request.
139+
* </p>
140+
* <p>
141+
* If you set a customizer to access a feature not implemented by Jaybird, please consider creating an improvement
142+
* ticket on <a href="https://github.com/FirebirdSQL/jaybird/issues">the Jaybird GitHub repository</a> as well.
143+
* </p>
144+
*
145+
* @param customizer
146+
* service request customizer, {@code null} to remove a previous customizer
147+
* @see #getServiceRequestCustomizer()
148+
* @see ServiceRequestCustomizer
149+
* @since 5.0.13
150+
*/
151+
void setServiceRequestCustomizer(ServiceRequestCustomizer customizer);
152+
153+
/**
154+
* @return service request customizer, {@code null} if none set
155+
* @see #setServiceRequestCustomizer(ServiceRequestCustomizer)
156+
* @since 5.0.13
157+
*/
158+
ServiceRequestCustomizer getServiceRequestCustomizer();
159+
135160
/**
136161
* Obtains the server version through a service call.
137162
*
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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+
* @since 5.0.13
31+
*/
32+
public final class ServiceRequestContext {
33+
34+
private final String operation;
35+
36+
/**
37+
* Creates a service request context.
38+
*
39+
* @param operation
40+
* name of the operation (the initiating service method name)
41+
*/
42+
public ServiceRequestContext(String operation) {
43+
this.operation = operation;
44+
}
45+
46+
/**
47+
* @return name of the operation
48+
*/
49+
public String operation() {
50+
return operation;
51+
}
52+
53+
}
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 5.0.13
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+
}

0 commit comments

Comments
 (0)