I was wondering if there was a way to pass arguments the a class extending the Service class from the Javafx concurrent package. I would like ProteinariumThread to take in a String argument like ClusterID seen below:
public class ProteinariumThread extends Service<String>{
String ClusterID = "q";
#Override
protected Task<String> createTask(){
return new Task<String>() {
#Override
protected String call() throws Exception {
updateMessage("Running Proteinarium");
System.out.println("Asleep");
ProteinariumRun.PRun(ClusterID);
System.out.println("Woke Up");
String woke = "Woke Up";
return woke;
}
};
}
}
Currently in order to run this background task I use the following bit of code:
final ProteinariumThread service = new ProteinariumThread();
service.start();
This however does not allow me to take in a String argument. Is there anyway to make it that service.start() is able to take in String arguments so that String variable ClusterID can come from outside of the ProteinariumThread class?
final ProteinariumThread service = new ProteinariumThread();
service.start(ClusterID);
You just need to give your service class a constructor and/or method which accepts the necessary argument. As services are meant to be reusable, it'd probably be best to allow configuration throughout the service's lifetime by exposing a property:
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
public class ProteinariumService extends Service<String> {
private final StringProperty clusterId = new SimpleStringProperty(this, "clusterId");
public final void setClusterId(String clusterId) { this.clusterId.set(clusterId); }
public final String getClusterId() { return clusterId.get(); }
public final StringProperty clusterIdProperty() { return clusterId; }
public ProteinariumService() {}
public ProteinariumService(String clusterId) {
setClusterId(clusterId);
}
#Override
protected Task<String> createTask() {
return new Task<>() {
final String clusterId = getClusterId(); // cache configuration
#Override
protected String call() throws Exception {
...
}
};
}
}
It's important you copy the needed state from the service to the task since the task is executed on a background thread.
Then when you need to change the cluster ID you just do:
// or bind the property to something in the UI (e.g. a TextField)
theService.setClusterId(newClusterId);
theService.start();
If you really want to be able to do that in one line you can always define an overload for start in your service class:
public void start(String clusterId) {
setClusterId(clusterId):
start();
}
Related
I'm using custom crudrespository to persist data in redis. However, I'm unable to autowire custom repository.
All the configuration seems correct and redis is running on my local.
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface CustomRepository extends CrudRepository<String,
Long> {
String get(String key);
void put(String key, String value);
}
//////////
public class StorageServiceImpl implements IStorageService {
#Autowired
private CustomRepository respository;
#Override
public void saveParameter() {
this.respository.put("key1","value1");
}
#Override
public String getParameter() {
return this.respository.get("key1");
}
/////
#Service
public interface IStorageService {
void saveParameter();
String getParameter();
}
///////
#SpringBootApplication(scanBasePackages = {"com.example.cache"})
#EnableRedisRepositories(basePackages = {"com.example.cache.repository"})
public class ApplicationConfiguration {
public static void main(String[] args){
SpringApplication.run(ApplicationConfiguration.class, args);
new StorageServiceImpl().saveParameter();
System.out.println(new StorageServiceImpl().getParameter());
}
}
When I try running this application using gradle bootRun, I get
Exception in thread "main" java.lang.NullPointerException
at com.example.cache.impl.StorageServiceImpl.saveParameter(StorageServiceImpl.java:16)
at com.example.cache.ApplicationConfiguration.main(ApplicationConfiguration.java:17)
Not sure what's wrong?
You can't use new on any bean, you need to #Autowire it. The annotations only work with spring managed beans at every level.
Add a new bean with a a storage service and a method that makes your call after it is created.
Also, I can't remember if the spring-boot creates the bean if there is only one implementation but I believe your StorageServiceImpl needs the #Service annotation, not the interface.
Delete this from your ApplicationConfiguration class.
new StorageServiceImpl().saveParameter();
System.out.println(new StorageServiceImpl().getParameter());
Then add this new class.
#Service
public class Startup {
#Autowired
IStorageService storageService;
#PostConstruct
public void init(){
storageService.saveParameter();
System.out.println(storageService().getParameter());
}
}
And you need a config
#Configuration
#EnableRedisRepositories
public class ApplicationConfig {
#Bean
public RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory();
}
#Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
return template;
}
}
I have difficulty understanding some Kafka concepts in Java Spring Boot. I’d like to test a consumer against a real Kafka broker running on a server, which has some producers that write / have already written data to various topics. I would like to establish a connection with the server, consume the data, and verify or process its content in a test.
An enormous majority of examples (actually all I have seen so far) in the internet refer to embedded kafka, EmbeddedKafkaBroker, and show both a producer and a consumer implemented on one machine, locally. I haven’t found any example that would explain how to make a connection with a remote kafka server and read data from a particular topic.
I've written some code and I've printed the broker address with:
System.out.println(embeddedKafkaBroker.getBrokerAddress(0));
What I got is 127.0.0.1:9092, which means that it is local, so the connection with the remote server has not been established.
On the other hand, when I run the SpringBootApplication I get the payload from the remote broker.
Receiver:
#Component
public class Receiver {
private static final String TOPIC_NAME = "X";
private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class);
private CountDownLatch latch = new CountDownLatch(1);
public CountDownLatch getLatch() {
return latch;
}
#KafkaListener(topics = TOPIC_NAME)
public void receive(final byte[] payload) {
LOGGER.info("received the following payload: '{}'", payload);
latch.countDown();
}
}
Config:
#EnableKafka
#Configuration
public class ByteReceiverConfig {
#Autowired
EmbeddedKafkaBroker kafkaEmbeded;
#Value("${spring.kafka.bootstrap-servers}")
private String bootstrapServers;
#Value("${spring.kafka.consumer.group-id}")
private String groupIdConfig;
#Bean
public KafkaListenerContainerFactory<?> kafkaListenerContainerFactory() {
final ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
#Bean
ConsumerFactory<Object, Object> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerProperties());
}
#Bean
Map<String, Object> consumerProperties() {
final Map<String, Object> properties =
KafkaTestUtils.consumerProps("junit-test", "true", this.kafkaEmbeded);
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupIdConfig);
return properties;
}
Test:
#EnableAutoConfiguration
#EnableKafka
#SpringBootTest(classes = {ByteReceiverConfig.class, Receiver.class})
#EmbeddedKafka
#ContextConfiguration(classes = ByteReceiverConfig.class)
#TestPropertySource(properties = { "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}",
"spring.kafka.consumer.group-id=EmbeddedKafkaTest"})
public class KafkaTest {
#Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;
#Autowired
EmbeddedKafkaBroker embeddedKafkaBroker;
#Autowired
Receiver receiver;
#BeforeEach
void waitForAssignment() {
for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry.getListenerContainers()) {
System.out.println(messageListenerContainer.getAssignedPartitions().isEmpty());
System.out.println(messageListenerContainer.toString());
System.out.println(embeddedKafkaBroker.getTopics().size());
System.out.println(embeddedKafkaBroker.getPartitionsPerTopic());
System.out.println(embeddedKafkaBroker.getBrokerAddress(0));
System.out.println(embeddedKafkaBroker.getBrokersAsString());
ContainerTestUtils.waitForAssignment(messageListenerContainer,
embeddedKafkaBroker.getPartitionsPerTopic());
}
#Test
public void testReceive() {
}
}
I would like somebody to shed some light on the following issues:
1.Can an instance of the class EmbeddedKafkaBroker be used to test data that comes from a remote broker, or is it only used for local tests, in which I would procude i.e send data to a topic that I created and consume data myself?
2.Is it possible to write a test class for a real kafka server? For instance to verify if a connection has been establish, or if a data has been read from a specific topic. What annotations, configurations, and classes would be needed in such case?
3.If I only want to consume data, do I have to provide the producer configuration in a config file (it would be strange, but all examples I have encountered so far did it)?
4.Do you know any resources (books, websites etc.) that show real examples of using kafka i.e. with a remote kafka server, with a procuder or a consumer only?
You don't need an embedded broker at all if you want to talk to an external broker only.
Yes, just set the bootstrap servers property appropriately.
No, you don't need producer configuration.
EDIT
#SpringBootApplication
public class So56044105Application {
public static void main(String[] args) {
SpringApplication.run(So56044105Application.class, args);
}
#Bean
public NewTopic topic() {
return new NewTopic("so56044105", 1, (short) 1);
}
}
spring.kafka.bootstrap-servers=10.0.0.8:9092
spring.kafka.consumer.enable-auto-commit=false
#RunWith(SpringRunner.class)
#SpringBootTest(classes = { So56044105Application.class, So56044105ApplicationTests.Config.class })
public class So56044105ApplicationTests {
#Autowired
public Config config;
#Test
public void test() throws InterruptedException {
assertThat(config.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(config.received.get(0)).isEqualTo("foo");
}
#Configuration
public static class Config implements ConsumerSeekAware {
List<String> received = new ArrayList<>();
CountDownLatch latch = new CountDownLatch(3);
#KafkaListener(id = "so56044105", topics = "so56044105")
public void listen(String in) {
System.out.println(in);
this.received.add(in);
this.latch.countDown();
}
#Override
public void registerSeekCallback(ConsumerSeekCallback callback) {
}
#Override
public void onPartitionsAssigned(Map<TopicPartition, Long> assignments, ConsumerSeekCallback callback) {
System.out.println("Seeking to beginning");
assignments.keySet().forEach(tp -> callback.seekToBeginning(tp.topic(), tp.partition()));
}
#Override
public void onIdleContainer(Map<TopicPartition, Long> assignments, ConsumerSeekCallback callback) {
}
}
}
There are some examples in this repository for bootstrapping real Kafka producers and consumers across a variety of configurations — plaintext, SSL, with and without authentication, etc.
Note: the repo above contains examples for the Effective Kafka book, which I am the author of. However, they can be used freely without the book and hopefully they make just as much sense on their own.
More to the point, here are a pair of examples for a basic producer and a consumer.
/** A sample Kafka producer. */
import static java.lang.System.*;
import java.util.*;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;
public final class BasicProducerSample {
public static void main(String[] args) throws InterruptedException {
final var topic = "getting-started";
final Map<String, Object> config =
Map.of(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName(),
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName(),
ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
try (var producer = new KafkaProducer<String, String>(config)) {
while (true) {
final var key = "myKey";
final var value = new Date().toString();
out.format("Publishing record with value %s%n",
value);
final Callback callback = (metadata, exception) -> {
out.format("Published with metadata: %s, error: %s%n",
metadata, exception);
};
// publish the record, handling the metadata in the callback
producer.send(new ProducerRecord<>(topic, key, value), callback);
// wait a second before publishing another
Thread.sleep(1000);
}
}
}
}
/** A sample Kafka consumer. */
import static java.lang.System.*;
import java.time.*;
import java.util.*;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.*;
public final class BasicConsumerSample {
public static void main(String[] args) {
final var topic = "getting-started";
final Map<String, Object> config =
Map.of(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName(),
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName(),
ConsumerConfig.GROUP_ID_CONFIG, "basic-consumer-sample",
ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest",
ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
try (var consumer = new KafkaConsumer<String, String>(config)) {
consumer.subscribe(Set.of(topic));
while (true) {
final var records = consumer.poll(Duration.ofMillis(100));
for (var record : records) {
out.format("Got record with value %s%n", record.value());
}
consumer.commitAsync();
}
}
}
}
Now, these are obviously not unit tests. But with very little rework they could be turned into one. The next step would be to remove Thread.sleep() and add assertions. Note, since Kafka is inherently asynchronous, naively asserting a published message in a consumer immediately after publishing will fail. For a robust, repeatable test, you may want to use something like Timesert.
I'm trying to implement a Contract test on Java as described there.
I paste the first test's code in my project and changed import static net.corda.testing.NodeTestUtils.ledger; to import static net.corda.testing.node.NodeTestUtils.ledger;
package com.template;
import org.junit.Test;
import static net.corda.testing.node.NodeTestUtils.ledger;
public class CommercialPaperTest {
#Test
public void emptyLedger() {
ledger(l -> {
return null;
});
}
}
And I see that ledger method has an absolutely different signature, so Java says that it cannot resolve method ledger(<lambda expression>).
What am I doing wrong?
There is an error on that page. The first argument to ledger should be a MockServices instance.
For example, we might write:
public class CommercialPaperTest {
private static final TestIdentity megaCorp = new TestIdentity(new CordaX500Name("MegaCorp", "London", "GB"));
private MockServices ledgerServices;
#Before
public void setUp() {
ledgerServices = new MockServices(
singletonList("net.corda.finance.contracts"),
megaCorp,
makeTestIdentityService(megaCorp.getIdentity())
);
}
#Test
public void emptyLedger() {
ledger(ledgerServices, l -> {
return null;
});
}
}
What is recommended/best way to validate the post request DTO bean ?
If validation failed I need to send customized error message like
{
"code": "invalid_fields",
"fields": {
"email": "Required",
"password": "Required",
}
}
DTO model
public class SignUpRequest {
#JsonProperty("email")
String email;
#JsonProperty("password")
String password;
public Result validate(){
}
}
controller
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
SignUpRequest DTO has the method validate.
What is the spring way of doing the validation ?
Thanks.
You can use the following technique.
add the following dependencies in your gradle/maven file
compile "javax.validation:validation-api:2.0.1.Final"
compile "org.hibernate.validator:hibernate-validator:6.0.9.Final"
Hibernate-validator is implementation of validation-api 2.0
Add Validated annotation to your controller class
import org.springframework.validation.annotation.Validated;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
}
Add Valid annotation to your method parameter
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
#RestController
#RequestMapping(value = "/contact")
#Validated
public class ContactController{
#PostMapping(value = "/register")
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest) {
Result result = signUpRequest.validate();
return new ResponseEntity<>(x, HttpStatus.OK);
}
}
Add Validated annotation to your dto class
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Email;
#Validated
public class SignUpRequest {
#JsonProperty("email")
#Email
String email;
#JsonProperty("password")
#NotNull
String password;
}
Add ExceptionTranslator with RestControllerAdvice annotation
#RestControllerAdvice
public class ExceptionTranslator {
/**
* Exception handler for validation errors caused by method parameters #RequesParam, #PathVariable, #RequestHeader annotated with javax.validation constraints.
*/
#ExceptionHandler
protected ResponseEntity<?> handleConstraintViolationException(ConstraintViolationException exception) {
List<ApiError> apiErrors = new ArrayList<>();
for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
String value = (violation.getInvalidValue() == null ? null : violation.getInvalidValue().toString());
apiErrors.add(new ApiError(violation.getPropertyPath().toString(), value, violation.getMessage()));
}
return ResponseEntity.badRequest().body(apiErrors);
}
}
Create ApiError class
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ApiError {
#JsonIgnore
private int code;
private String field;
private String value;
private String message;
public ApiError(String message) {
this.message = message;
}
public ApiError(String field, String value, String message) {
this.field = field;
this.value = value;
this.message = message;
}
}
Now if password field is missed you'll see the following response structure:
[
{
"field": "password",
"message": "must be filled"
}
]
If you would like to use some custom logic to validate your fields you may use the following approach
Create specific annotation class
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Constraint(validatedBy = ContactRequiredParametersValidator.class)
#Target({ METHOD, CONSTRUCTOR })
#Retention(RUNTIME)
#Documented
public #interface ContactRequiredParameters {
String message() default
"Email or phone must be filled";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Create custom validator
import org.apache.commons.lang.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.constraintvalidation.SupportedValidationTarget;
import javax.validation.constraintvalidation.ValidationTarget;
#SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ContactRequiredParametersValidator implements ConstraintValidator<ContactRequiredParameters, Object[]> {
#Override
public boolean isValid(Object[] value,
ConstraintValidatorContext context) {
if (value[0] == null) {
return true;
}
if (!(value[0] instanceof SignUpRequest)) {
throw new IllegalArgumentException(
"Illegal method signature, expected two parameters of type LocalDate.");
}
SignUpRequest contact = (SignUpRequest) value[0];
return StringUtils.isNotEmpty(contact.getPassword());
}
}
add #ContactRequiredParameters annotation to your method in controller
#PostMapping(value = "/register")
#ContactRequiredParameters
public ResponseEntity<Object> signupRider(#Valid #RequestBody SignUpRequest signUpRequest)
That's all. Hope it helps
Spring boot supports validation out of the box using validation-api which is included with spring web mvc starter:
#RestController
#RequiredArgsConstructor
public class TestController {
#PutMapping(value = "/", consumes = APPLICATION_JSON_VALUE)
#ResponseStatus(NO_CONTENT)
public void test(#Valid #RequestBody final SignUpRequest params) {
...
}
}
You can annotate your SignUpRequest using annotations such as javax.validation.constraints.NotNull and other more complex ones.
the error messages can be customised with message properties or hard coded strings if i18n/l10n is of less interest to you.
Sample here: https://spring.io/guides/gs/validating-form-input/
If you want behaviour outside of the provided annotations you can write a custom annotation that can do that, e.g.
#Target({FIELD})
#Retention(RUNTIME)
#Constraint(validatedBy = NotPastValidator.class)
#Documented
public #interface NotPast {
String message() default "date must not be in the past";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then:
public class NotPastValidator implements ConstraintValidator<NotPast, LocalDate> {
#Override
public void initialize(final NotPast constraintAnnotation) {
// nothing to do.
}
#Override
public boolean isValid(final LocalDate value, final ConstraintValidatorContext context) {
// As the Bean Validation specification recommends, we consider null values as being valid.
return value == null || isDateNotPast(value);
}
private boolean isDateNotPast(final LocalDate value) {
return ...
}
}
And finally just annotate your field:
#NotPast
Of course this is just an example with some code I previously used, you'll need to adapt to your needs.
If you don't want to use the validator API at all you can equally just write your own code to programatically check and throw some type of custom exception when invalid. This can then be caught in the controller and you can send what ever response you want, e.g.
#RestController
public class PaymentController {
#PostMapping(value ="/", consumes = APPLICATION_JSON_VALUE)
public void makePayment(#RequestBody final PaymentParams params) {
// validationService.validate(params);
}
#ExceptionHandler(MyValidationException.class)
public ResponseEntity<ExceptionDto> paymentCardException(final MyValidationException e) {
return status(BAD_REQUEST)
.contentType(APPLICATION_JSON)
.body(new ExceptionDto(e.getMessage));
}
}
I'd say given the validation API is well supported by spring, to me it makes sense to apply declarative validations where possible when using this stack. Custom rules can be a little painful, but you can use a multi faceted approach with some annotation based and equally you can perform some more complex validations in your own service.
This is a custom validation.
#PostMapping
private ResponseEntity<?> addMessage(#RequestBody Message message) {
Map<String, String> response = new HashMap<>();
if (message.getInputMessage() == null || message.getInputMessage().equals("")) {
response.put("status", "E");
response.put("message", "input message can not be empty");
return ResponseEntity.ok(response);
}
int id = messageService.addMessage(message);
if (id <= 0) {
response.put("status", "E");
response.put("message", "add message has error");
return ResponseEntity.ok(response);
}
response.put("status", "S");
response.put("message", "success");
return ResponseEntity.ok(response);
}
I have the following class:
public static class ARestRequestParam
{
String name;
LocalDate date; // joda type
}
And I want it to be deserialized from the following JSON which is processed by jackson.
{ name:"abc", date:"20131217" }
Actually, I want to deserialize any LocalDate field in any class with "yyyyMMdd" format, without duplicating the format string, without adding any setter method, without any XML configuration. (That is, annotation and Java code is preferable)
How can it be done?
Also, I also want to know the serialization part. that is, LocalDate -> "yyyyMMdd".
I've seen followings:
jackson-datatype-joda (https://github.com/FasterXML/jackson-datatype-joda)
custom serializer (public class JodaDateTimeJsonSerializer extends JsonSerializer { ... } - Spring #ResponseBody Jackson JsonSerializer with JodaTime)
#JsonCreator
#DateTimeFormat
But I don't know which is applicable, and which is most up-to-date.
BTW, I use Spring Boot.
UPDATE
Ok, I have managed to write working code for the deserialization part.
It is as follows:
#Configuration
#EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters)
{
converters.add(jacksonConverter());
}
#Bean
public MappingJackson2HttpMessageConverter jacksonConverter()
{
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ApiJodaModule());
converter.setObjectMapper(mapper);
return converter;
}
#SuppressWarnings("serial")
private class ApiJodaModule extends SimpleModule
{
public ApiJodaModule()
{
addDeserializer(LocalDate.class, new ApiLocalDateDeserializer());
}
}
#SuppressWarnings("serial")
private static class ApiLocalDateDeserializer
extends StdScalarDeserializer<LocalDate>
{
private static DateTimeFormatter formatter =
DateTimeFormat.forPattern("yyyyMMdd");
public ApiLocalDateDeserializer() { super(LocalDate.class); }
#Override
public LocalDate deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (jp.getCurrentToken() == JsonToken.VALUE_STRING)
{
String s = jp.getText().trim();
if (s.length() == 0)
return null;
return LocalDate.parse(s, formatter);
}
throw ctxt.wrongTokenException(jp, JsonToken.NOT_AVAILABLE,
"expected JSON Array, String or Number");
}
}
}
I had to implement the deserializer myself, since the datetime format for the deserializer in jackson-datatype-joda cannot be altered. So, since I've implemented the deserializer myself, jackson-datatype-joda is not needed. (although I've copied pieces of its code)
Is this code Ok?
Is this up-to-date solution?
Is there any other easier way?
Any suggestion would be greatly appreciated.
UPDATE
Following Dave Syer's suggestion, I modified the source above as follows:
Removed 2 methods: configureMessageConverters(), jacksonConverter()
Added following method into WebMvcConfiguration class:
#Bean
public Module apiJodaModule()
{
return new ApiJodaModule();
}
But now it does not work. It seems apiJodaModule() is ignored.
How can I make it work?
(It seems that I should not have a class that has #EnableWebMvc to use that feature.)
The version I use is org.springframework.boot:spring-boot-starter-web:0.5.0.M6.
UPDATE
Final working version is as follows: (with other configurations I've done previously in the class that had #EnableWebMvc)
As Dave Syer mentioned, this will only work on BUILD-SNAPSHOT version, at least for now.
#Configuration
public class WebMvcConfiguration
{
#Bean
public WebMvcConfigurerAdapter apiWebMvcConfiguration()
{
return new ApiWebMvcConfiguration();
}
#Bean
public UserInterceptor userInterceptor()
{
return new UserInterceptor();
}
public class ApiWebMvcConfiguration extends WebMvcConfigurerAdapter
{
#Override
public void addInterceptors(InterceptorRegistry registry)
{
registry.addInterceptor(userInterceptor())
.addPathPatterns("/api/user/**");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry)
{
registry.addResourceHandler("/**")
.addResourceLocations("/")
.setCachePeriod(0);
}
}
#Bean
public Module apiJodaModule()
{
return new ApiJodaModule();
}
#SuppressWarnings("serial")
private static class ApiJodaModule extends SimpleModule
{
public ApiJodaModule()
{
addDeserializer(LocalDate.class, new ApiLocalDateDeserializer());
}
private static final class ApiLocalDateDeserializer
extends StdScalarDeserializer<LocalDate>
{
public ApiLocalDateDeserializer() { super(LocalDate.class); }
#Override
public LocalDate deserialize(JsonParser jp,
DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
if (jp.getCurrentToken() == JsonToken.VALUE_STRING)
{
String s = jp.getText().trim();
if (s.length() == 0)
return null;
return LocalDate.parse(s, localDateFormatter);
}
throw ctxt.mappingException(LocalDate.class);
}
}
private static DateTimeFormatter localDateFormatter =
DateTimeFormat.forPattern("yyyyMMdd");
}
}
Your code is OK, but if you use #EnableWebMvc in a Spring Boot app you switch off the default settings in the framework, so maybe you should avoid that. Also, you now have only one HttpMessageConverter in your MVC handler adapter. If you use a snapshot of Spring Boot you ought to be able to simply define a #Bean of type Module and everything else would be automatic, so I would recommend doing it that way.