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
object
as a request and response. Never anarray
,string
,decimal
,boolean
ornull
. - 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
- customExchangeFilterFunction
for applying a filter during communicationdefaultHttpHeaders
- customHttpHeaders
to 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 withonSuccess
andonError
consumersgetObject
- a blocking GET call withObjectResponse
post
- a blocking POST call with a generic request / responsepostNonBlocking
- a non-blocking POST call with a generic request / response withonSuccess
andonError
consumerspostObject
- a blocking POST call withObjectRequest
/ObjectResponse
put
- a blocking PUT call with a generic request / responseputNonBlocking
- a non-blocking PUT call with a generic request / response withonSuccess
andonError
consumersputObject
- a blocking PUT call withObjectRequest
/ObjectResponse
delete
- a blocking DELETE call with a generic responsedeleteNonBlocking
- a non-blocking DELETE call with a generic response withonSuccess
andonError
consumersdeleteObject
- a blocking DELETE call withObjectResponse
patch
- a blocking PATCH call with a generic request / responsepatchNonBlocking
- a non-blocking PATCH call with a generic request / response withonSuccess
andonError
consumerspatchObject
- a blocking PATCH call withObjectRequest
/ObjectResponse
head
- a blocking HEAD call with a generic requestheadNonBlocking
- a non-blocking HEAD call with a generic request withonSuccess
andonError
consumersheadObject
- a blocking HEAD call withObjectRequest
The path
parameter specified in requests can be either:
- a partial request path, in this case the
baseUrl
parameter must be configured during initialization - a full URL, in this case the
baseUrl
parameter 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 theObjectResponse
response 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. 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
@EnableScheduling
annotation on class annotated with@SpringBootApplication
so that theflush
andcleanup
functionality can be scheduled. - Add the
com.wultra.core.audit.base
package 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.name
property 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
)
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.