This project contains Wultra Java Core classes that are shared across our projects.
All Wultra projects that use RESTful API to publish their services use common model structure. The core philosophy is following:
- We use JSON format to transfer the data.
- We always transfer an
objectas a request and response. Never anarray,string,decimal,booleanornull. - The top most object is used for a "transport" information (paging, encryption, status, ...), the actual "business logic" information is embedded in request / response object attribute of the top-most object.
All requests have a following JSON structure:
{
"requestObject": {
"_comment": "Request object attributes"
}
}To prepare a request with request object in Java, use:
new ObjectRequest(someRequestObject);For a simple OK response, we use following format:
{
"status": "OK"
}To prepare a simple success response in Java, use:
new Response();For an OK response with a response object, we use following format:
{
"status": "OK",
"responseObject": {
"_comment": "Request object attributes"
}
}Note that the response object may be a list of other objects, strings, decimals or booleans:
{
"status": "OK",
"responseObject": [
"_item1",
"_item2"
]
}To prepare a success response with a response object in Java, use:
new ObjectResponse(someObjectResponse);For an error response, we use following format that includes an error code and message for easier debugging:
{
"status": "ERROR",
"responseObject": {
"code": "SOME_ERROR_CODE",
"message": "Some message, for debugging purposes"
}
}To prepare an error response with an error details in Java, use:
new ErrorResponse("SOME_ERROR_CODE", "Some message, for debugging purposes");Class DefaultRestClient provides a base implementation of a REST client. The client provides an interface for calling HTTP methods: GET, POST, PUT, and DELETE.
The example below shows very basic initialization and usage of the REST client without any configuration:
RestClient restClient = new DefaultRestClient("http://localhost");
ResponseEntity<String> responseEntity = restClient.get("/api/status", new ParameterizedTypeReference<String>() {});
String response = responseEntity.getBody();
HttpHeaders headers = responseEntity.getHeaders();In order to configure the REST client, you can use the builder interface:
RestClient restClient = DefaultRestClient.builder().baseUrl("http://localhost").build();The following options are available for the builder:
baseUrl- base URL for all requests, full URL is expected in request path if baseUrl is not specifiedcontentType- content type used for requests (default:APPLICATION_JSON)acceptType- accept type used for signalling the response type (default:APPLICATION_JSON)proxy- proxy settings (default: proxy is disabled)host- proxy hostport- proxy portusername- proxy usernamepassword- proxy password
connectionTimeout- connection timeout in milliseconds (default: 5000 ms)responseTimeout- Maximum duration allowed between each network-level read operations. (default: no timeout)maxIdleTime- ConnectionProvider max idle time. (default: no max idle time)maxLifeTime- ConnectionProvider max life time. (default: no max life time)keepAliveEnabled- Keep-Alive probe feature flag (default: false)keepAliveIdle- Keep-Alive idle timekeepAliveInterval- Keep-Alive retransmission interval timekeepAliveCount- Keep-Alive retransmission limitacceptInvalidSslCertificate- whether invalid SSL certificate is accepted (default: false)maxInMemorySize- maximum in memory request size (default: 1048576 bytes)httpBasicAuth- HTTP basic authentication (default: disabled)username- username for HTTP basic authenticationpassword- password for HTTP basic authentication
httpDigestAuth- HTTP digest authentication (default: disabled)username- username for HTTP digest authenticationpassword- password for HTTP digest authentication
certificateAuth- certificate authentication (default: disabled)useCustomKeyStore- whether custom keystore should be used for certificate authentication (default: false)keyStoreLocation- resource location of keystore (e.g.file:/path_to_keystore)keyStorePassword- keystore passwordkeyStoreBytes- byte data with keystore (alternative configuration way tokeyStoreLocation)keyAlias- key alias for the private key stored inside the keystorekeyPassword- password for the private key stored inside the keystoreuseCustomTrustStore- whether custom truststore should be used for certificate authentication (default: false)trustStoreLocation- resource location of truststore (e.g.file:/path_to_truststore)trustStorePassword- truststore passwordtrustStoreBytes- byte data with truststore (alternative configuration way totrustStoreLocation)
modules- jackson modulesjacksonProperties- jackson properties for custom object mapperserialization- Jackson on/off features that affect the way Java objects are serialized.deserialization- Jackson on/off features that affect the way Java objects are deserialized, e.g.FAIL_ON_UNKNOWN_PROPERTIES=true
filter- customExchangeFilterFunctionfor applying a filter during communicationdefaultHttpHeaders- customHttpHeadersto be added to all requests as default HTTP headersfollowRedirectEnabled- whether HTTP redirect responses are followed by the client (default: false)simpleLoggingEnabled- whether simple one-line logging of HTTP method, URL and response status code is enabled (default: false)logErrorResponsesAsWarnings- whether responses with error status codes are logged on WARN level in simple logging (default: true)
Once the rest client is initialized, you can use the following methods. Each method has two variants so that HTTP headers can be specified, if necessary. The following methods are available:
get- a blocking GET call with a generic responsegetNonBlocking- a non-blocking GET call with a generic response withonSuccessandonErrorconsumersgetObject- a blocking GET call withObjectResponsepost- a blocking POST call with a generic request / responsepostNonBlocking- a non-blocking POST call with a generic request / response withonSuccessandonErrorconsumerspostObject- a blocking POST call withObjectRequest/ObjectResponseput- a blocking PUT call with a generic request / responseputNonBlocking- a non-blocking PUT call with a generic request / response withonSuccessandonErrorconsumersputObject- a blocking PUT call withObjectRequest/ObjectResponsedelete- a blocking DELETE call with a generic responsedeleteNonBlocking- a non-blocking DELETE call with a generic response withonSuccessandonErrorconsumersdeleteObject- a blocking DELETE call withObjectResponsepatch- a blocking PATCH call with a generic request / responsepatchNonBlocking- a non-blocking PATCH call with a generic request / response withonSuccessandonErrorconsumerspatchObject- a blocking PATCH call withObjectRequest/ObjectResponsehead- a blocking HEAD call with a generic requestheadNonBlocking- a non-blocking HEAD call with a generic request withonSuccessandonErrorconsumersheadObject- a blocking HEAD call withObjectRequest
The path parameter specified in requests can be either:
- a partial request path, in this case the
baseUrlparameter must be configured during initialization - a full URL, in this case the
baseUrlparameter must not be configured during initialization
The example below shows how to use the Rest Client with ObjectRequest / ObjectResponse classes.
RestClient restClient = DefaultRestClient.builder()
.baseUrl("http://localhost:8080/my-app")
.build();
// The requestData object contains data object which is serialized and sent to the server
RequestData requestData = new RequestData(...);
ObjectRequest<RequestData> objectRequest = new ObjectRequest<RequestData>(requestData);
try {
ObjectResponse<ResponseData> objectResponse = restClient.postObject("/api/endpoint", objectRequest, ResponseData.class);
// The responseData object contains deserialized response received from the server
ResponseData responseData = objectResponse.getResponseObject();
} catch (RestClientException ex) {
if (ex.getStatusCode() == HttpStatus.BAD_REQUEST) {
// handle BAD_REQUEST error
}
...
}In case any HTTP error occurs during a blocking HTTP request execution, a RestClientException is thrown with following details:
statusCode- an HTTP status coderesponse- a raw error responseresponseHeaders- response HTTP headerserrorResponse- a parsedErrorResponse, only used for theObjectResponseresponse type
Non-blocking methods provide an onError consumer for custom error handling.
You can enable simple one-line logging using RestClientConfiguration:
config.setSimpleLoggingEnabled(true);The log messages use INFO and WARN levels based on the status code:
2023-01-31 12:09:14.014 INFO 64851 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : RestClient GET https://localhost:49728/api/test/response: 200 OK
2023-01-31 12:09:15.367 WARN 64851 --- [ctor-http-nio-4] c.w.c.r.client.base.DefaultRestClient : RestClient POST https://localhost:49728/api/test/error-response: 400 BAD_REQUEST
You can disable logging on WARN level, in this case log messages always use the INFO level:
config.setLogErrorResponsesAsWarnings(false);To enable detailed request / response logging, set level of com.wultra.core.rest.client.base.DefaultRestClient to TRACE.
2022-11-25 07:40:37.283 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35] REGISTERED
2022-11-25 07:40:37.297 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35] CONNECT: localhost/127.0.0.1:50794
2022-11-25 07:40:37.323 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] ACTIVE
2022-11-25 07:40:37.396 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] READ COMPLETE
2022-11-25 07:40:37.396 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] READ COMPLETE
2022-11-25 07:40:37.436 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] USER_EVENT: SslHandshakeCompletionEvent(SUCCESS)
2022-11-25 07:40:37.466 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35-1, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] WRITE: 212B POST /api/test/object-response HTTP/1.1
user-agent: ReactorNetty/1.0.19
host: localhost:50794
Content-Type: application/json
Accept: application/json
Authorization: Basic dGVzdDp0ZXN0
content-length: 45
2022-11-25 07:40:37.466 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35-1, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] WRITE: 45B {"requestObject":{"request":"1669358437187"}}
2022-11-25 07:40:37.466 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35-1, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] FLUSH
2022-11-25 07:40:37.470 TRACE 53194 --- [ctor-http-nio-2] c.w.c.r.client.base.DefaultRestClient : [4400ac35-1, L:/127.0.0.1:50795 - R:localhost/127.0.0.1:50794] READ COMPLETE
2022-11-25 07:35:07.393 TRACE 53095 --- [tor-http-nio-10] c.w.c.r.client.base.DefaultRestClient : [9855567c-1, L:/127.0.0.1:50699 - R:localhost/127.0.0.1:50690] READ: 430B HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Fri, 25 Nov 2022 06:35:06 GMT
3f
{"status":"OK","responseObject":{"response":"object response"}}
2022-11-25 07:35:07.393 TRACE 53095 --- [tor-http-nio-10] c.w.c.r.client.base.DefaultRestClient : [9855567c-1, L:/127.0.0.1:50699 - R:localhost/127.0.0.1:50690] READ COMPLETE
The audit-base project provides auditing functionality for easier investigation of issues. Audit records are stored in a database and can be easily queried. The auditing library also handles removal of old audit records.
The audit library requires one database table audit_log and optionally the second table audit_params for logging detail parameters.
Also the shedlock table is required for locking scheduled tasks.
The DDL is available for the following databases:
The following configuration is required for integration of the auditing library:
- Enable scheduling on the application using
@EnableSchedulingannotation on class annotated with@SpringBootApplicationso that theflushandcleanupfunctionality can be scheduled. In order to enable schedule locking use@EnableSchedulerLockannotation and configure theLockProviderbean, see ShedLock documentation for details. - Add the
com.wultra.core.audit.basepackage to the@ComponentScan, e.g.@ComponentScan(basePackages = {"...", "com.wultra.core.audit.base"}), so that the annotations used in auditing library can be discovered. - Configure the
spring.application.nameproperty to enable storing application name with audit records.
The following properties can be configured in case the default configuration needs to be changed:
audit.level- minimum audit level (default:INFO)audit.event.queue.size- event queue size in memory (default:100000)audit.storage.type- storage type, reserved for future use (default:DATABASE)audit.db.cleanup.days- audit records older than specified number of days are deleted (default:365)audit.db.table.log.name- name of audit log database table (default:audit_log)audit.db.table.param.name- name of audit parameters database table (default:audit_param)audit.db.table.param.enabled- flag if logging params to parameters database is enabled (default:false)audit.db.batch.size- database batch size (default:1000)audit.cleanup.cron- A cron expression for the cleanup job. (default:0 0 * * * *, use-to turn it off completely)audit.cleanup.lockAtLeastFor- The lock will be held at least for given duration. (default:5s)audit.cleanup.lockAtMostFor- The lock will be held at most for given duration. (default:30m)
You can configure database schema used by the auditing library using regular Spring JPA/Hibernate property in your application:
spring.jpa.properties.hibernate.default_schema- database database schema (default: none)
Following audit levels are available:
error- an error occurredwarn- a minor error occurredinfo- informational messagedebug- debug message (disabled by default)trace- trace message (disabled by default)
Initialization of audit factory:
@Configuration
@ComponentScan(basePackages = {"com.wultra.core.audit.base"})
public class WebServerConfiguration {
private final AuditFactory auditFactory;
@Autowired
public WebServerConfiguration(AuditFactory auditFactory) {
this.auditFactory = auditFactory;
}
@Bean
public Audit audit() {
return auditFactory.getAudit();
}
}Autowiring:
public class MyClass {
private final Audit audit;
@Autowired
public MyClass(Audit audit) {
this.audit = audit;
}
}Basic usage:
audit.info("a message");Formatting messages:
audit.info("a message with {}", "formatting");Auditing with specified level:
audit.log("a message for error level", AuditLevel.ERROR);Auditing of exceptions:
audit.warn("a message", new Exception("an exception"));Auditing with parameters:
audit.info("a message", AuditDetail.builder().param("my_id", "some_id").build());Auditing with parameters and type of audit message:
String operationId = UUID.randomUUID().toString();
Map<String, Object> param = new LinkedHashMap<>();
param.put("user_id", "some_id");
param.put("operation_id", operationId);
audit.info("an access message", AuditDetail.builder().type("ACCESS").params(param).build());The http-common project provides common functionality for HTTP stack.
RequestContextConverter converts HttpServletRequest to a Wultra specific class RequestContext.
This context object contains user agent and best-effort guess of the client IP address.
The annotations project provides common annotations.
Right now, these annotations are available:
PublicApi- Marker for interfaces intended to be called by extension.PublicSpi- Marker for interfaces intended to be implemented by extensions and called by core.