Test a Reactive-Kafka Consumer and Producer Template using embedded kafka + custom serialised - spring-kafka

We need an example on how to test ReactiveKafkaConsumerTemplate and ReactiveKafkaProducerTemplate with an embedded-kafka-broker. Thanks.
CORRECT CODE IS HERE AFTR DISCUSSION
You can have your custom de-serializer accordingly to use custom ReactiveKafkaConsumerTemplate
Custom Serialiser:
import org.apache.kafka.common.serialization.Serializer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class EmployeeSerializer implements Serializer<Employee> {
#Override
public byte[] serialize(String topic, Employee data) {
byte[] rb = null;
ObjectMapper mapper = new ObjectMapper();
try {
rb = mapper.writeValueAsString(data).getBytes();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return rb;
}
}
Use it part of embedded-kfka-reactive test:
import java.util.Map;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.connect.json.JsonSerializer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.kafka.core.reactive.ReactiveKafkaProducerTemplate;
import org.springframework.kafka.support.converter.MessagingMessageConverter;
import org.springframework.kafka.test.condition.EmbeddedKafkaCondition;
import org.springframework.kafka.test.context.EmbeddedKafka;
import org.springframework.kafka.test.utils.KafkaTestUtils;
import reactor.kafka.sender.SenderOptions;
import reactor.kafka.sender.SenderRecord;
import reactor.test.StepVerifier;
#EmbeddedKafka(topics = EmbeddedKafkareactiveTest.REACTIVE_INT_KEY_TOPIC,
brokerProperties = { "transaction.state.log.replication.factor=1", "transaction.state.log.min.isr=1" })
public class EmbeddedKafkareactiveTest {
public static final String REACTIVE_INT_KEY_TOPIC = "reactive_int_key_topic";
private static final Integer DEFAULT_KEY = 1;
private static final String DEFAULT_VERIFY_TIMEOUT = null;
private ReactiveKafkaProducerTemplate<Integer, Employee> reactiveKafkaProducerTemplate;
#BeforeEach
public void setUp() {
reactiveKafkaProducerTemplate = new ReactiveKafkaProducerTemplate<>(setupSenderOptionsWithDefaultTopic(),
new MessagingMessageConverter());
}
private SenderOptions<Integer, Employee> setupSenderOptionsWithDefaultTopic() {
Map<String, Object> senderProps = KafkaTestUtils
.producerProps(EmbeddedKafkaCondition.getBroker().getBrokersAsString());
SenderOptions<Integer, Employee> senderOptions = SenderOptions.create(senderProps);
senderOptions = senderOptions.producerProperty(ProducerConfig.TRANSACTIONAL_ID_CONFIG, "reactive.transaction")
.producerProperty(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true)
.producerProperty(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class.getName())
;
return senderOptions;
}
#Test
public void test_When_Publish() {
Employee employee = new Employee();
ProducerRecord<Integer, Employee> producerRecord = new ProducerRecord<Integer, Employee>(REACTIVE_INT_KEY_TOPIC, DEFAULT_KEY, employee);
StepVerifier.create(reactiveKafkaProducerTemplate.send(producerRecord)
.then())
.expectComplete()
.verify();
}
#AfterEach
public void tearDown() {
reactiveKafkaProducerTemplate.close();
}
}

The tests in the framework use an embedded kafka broker.
https://github.com/spring-projects/spring-kafka/tree/main/spring-kafka/src/test/java/org/springframework/kafka/core/reactive
#EmbeddedKafka(topics = ReactiveKafkaProducerTemplateIntegrationTests.REACTIVE_INT_KEY_TOPIC, partitions = 2)
public class ReactiveKafkaProducerTemplateIntegrationTests {
...

added correct serialised with a non-transactional producer. please see the code on top of this page for the answer.

Related

Java Code to invoke PATCH method from JUnit

I have a simple MyLibraryApplication which is having code to invoke POST(TransactionControllerImpl.issueBookToMember) and PATCH(TransactionControllerImpl.returnBookTransaction) methods. I have referred some links on net and tried my best to write code to invoke PATCH method. The code can be found in TransactionControllerTest(testBookReturnUsingRestTemplate and testBookReturnUsingMockMvc methods). The code for invoking POST is working fine but the code for invoking PATCH is not working. Control never reaches returnBookTransaction inside TransactionControllerImpl.
Error: Invalid PATCH method.
I am looking for code snippet for TransactionControllerTest.testBookReturnUsingRestTemplate and testBookReturnUsingMockMvc methods. Can someone help me in getting this code into proper shape?
package com.mycompany.techtrial;
import java.util.Map;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import com.mycompany.techtrial.Transaction;
public interface TransactionController {
public ResponseEntity<Transaction> issueBookToMember(#RequestBody Map<String, String> params);
public ResponseEntity<Transaction> returnBookTransaction(#PathVariable(name="transaction-id") Long transactionId);
}
/**
*
*/
package com.mycompany.techtrial;
import java.time.LocalDateTime;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class TransactionControllerImpl implements TransactionController{
/*
* PLEASE DO NOT CHANGE SIGNATURE OR METHOD TYPE OF END POINTS
* Example Post Request : { "book":"Java8 Primer","member":"Test 1" }
*/
#PostMapping(path = "/api/transaction")
public ResponseEntity<Transaction> issueBookToMember(#RequestBody Map<String, String> params){
String book = params.get("book");
String member = params.get("member");
Transaction transaction = new Transaction();
transaction.setId(1L);
transaction.setBook(book);
transaction.setMember(member);
transaction.setDateOfIssue(LocalDateTime.now());
transaction.setDateOfReturn(Transaction.getDefaultReturnDate());
return ResponseEntity.ok().body(transaction);
}
/*
* PLEASE DO NOT CHANGE SIGNATURE OR METHOD TYPE OF END POINTS
*/
#PatchMapping(path= "/api/transaction/{transaction-id}/return")
public ResponseEntity<Transaction> returnBookTransaction(#PathVariable(name="transaction-id") Long transactionId){
String book = "Java8 Primer";
String member = "Test 1";
Transaction transaction = new Transaction();
transaction.setId(1L);
transaction.setBook(book);
transaction.setMember(member);
transaction.setDateOfIssue(LocalDateTime.now().minusDays(10));
transaction.setDateOfReturn(LocalDateTime.now());
return ResponseEntity.ok().body(transaction);
}
}
package com.mycompany.techtrial;
import java.util.HashMap;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class TransactionControllerTest {
MockMvc mockMvc;
#Mock
private TransactionController transactionController;
#Autowired
private TestRestTemplate template;
#Before
public void setup() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(transactionController).build();
}
#Test
public void testBookIssue() throws Exception {
HttpEntity<Object> transaction = getHttpEntity(
"{\"book\": \"Java8 Primer\", \"member\": \"Test 1\" }");
ResponseEntity<Transaction> response = template.postForEntity(
"/api/transaction", transaction, Transaction.class);
Assert.assertEquals("Java8 Primer", response.getBody().getBook());
Assert.assertEquals("Test 1", response.getBody().getMember());
Assert.assertEquals(200,response.getStatusCode().value());
}
#Test
public void testBookReturnUsingRestTemplate() throws Exception {
Long transactionId = new Long(1);
HashMap<String,Long> uriVariables = new HashMap<String,Long>();
uriVariables.put("transaction-id", transactionId);
Transaction transaction = template.patchForObject(
"/api/transaction/{transaction-id}/return",null, Transaction.class, uriVariables);
Assert.assertEquals(new Long(1), transaction.getId());
//Assert.assertEquals(200,response.getStatusCode().value());
}
#Test
public void testBookReturnUsingMockMvc() throws Exception {
Long transactionId = new Long(1);
HashMap<String,Long> uriVariables = new HashMap<String,Long>();
uriVariables.put("transaction-id", transactionId);
ResultActions obj = mockMvc.perform( MockMvcRequestBuilders
.patch("/api/transaction/{transaction-id}/return",transactionId)
.content("")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON));
System.out.println(obj.getClass());
HttpStatus status = obj.andReturn().getModelAndView().getStatus();
boolean success = status.is2xxSuccessful();
System.out.println("success="+success);
Assert.assertEquals(new Long(1), transactionId);
//Assert.assertEquals(200,response.getStatusCode().value());
}
private HttpEntity<Object> getHttpEntity(Object body) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<Object>(body, headers);
}
}
package com.mycompany.techtrial;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class Transaction implements Serializable {
private static final long serialVersionUID = 8951221480021840448L;
private static final LocalDateTime defaultReturnDate = LocalDateTime.of(LocalDate.of(2299, 12, 31), LocalTime.of(12, 0, 0));
Long id;
private String book;
private String member;
public String getBook() {
return book;
}
public void setBook(String book) {
this.book = book;
}
public String getMember() {
return member;
}
public void setMember(String member) {
this.member = member;
}
//Date and time of issuance of this book
LocalDateTime dateOfIssue;
//Date and time of return of this book
LocalDateTime dateOfReturn;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public LocalDateTime getDateOfIssue() {
return dateOfIssue;
}
public void setDateOfIssue(LocalDateTime dateOfIssue) {
this.dateOfIssue = dateOfIssue;
}
public LocalDateTime getDateOfReturn() {
return dateOfReturn;
}
public void setDateOfReturn(LocalDateTime dateOfReturn) {
this.dateOfReturn = dateOfReturn;
}
#Override
public String toString() {
return "Transaction [id=" + id + ", book=" + book + ", member=" + member + ", dateOfIssue=" + dateOfIssue + ", dateOfReturn=" + dateOfReturn + "]";
}
//#PrePersist
void preInsert() {
if (this.dateOfReturn == null)
this.dateOfReturn = defaultReturnDate;
}
public static LocalDateTime getDefaultReturnDate() {
return defaultReturnDate;
}
}
package com.mycompany.techtrial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class MyLibraryApplication {
public static void main(String[] args) {
SpringApplication.run(MyLibraryApplication.class, args);
}
}
It seems to be a known issue with the RestTemplate default Http client.
RestTemplate bug
A workaround for this would be to use the apache httpcomponents httpclient library in the RestTemplateBuilder.setRequestFactory and pass that in the constructor to TestRestTemplate
After that you can use the exchange method on the TestRestTemplate class and do a PATCH request.
Sample code to create TestRestTemplate:
Supplier<ClientHttpRequestFactory> supplier = () -> {
return new HttpComponentsClientHttpRequestFactory();
};
restTemplateBuilder.requestFactory(supplier);
TestRestTemplate testRestTemplate = new TestRestTemplate(restTemplateBuilder);
testRestTemplate.exchange("/api/transaction/{transaction-id}/return",HttpMethod.PATCH,null,Transaction.class,uriVariables);

SerializationWhitelist activated, but SerialFilter still rejects

I am making an implementation of a Notary, and I am encountering an issue caused by net.corda.core.SerialFilter. The simple test in the UniquenessProvider demonstrates the deserialization error despite my Whitelist implementation
My class SerializableCommitContainer
import com.sun.istack.Nullable;
import net.corda.core.contracts.TimeWindow;
import net.corda.core.contracts.StateRef;
import net.corda.core.crypto.SecureHash;
import net.corda.core.flows.NotarisationRequestSignature;
import net.corda.core.identity.Party;
import java.io.Serializable;
import java.util.List;
public class SerializableCommitContainer implements Serializable {
private byte[] inputStates;
private byte[] txID;
private byte[] callerIdentity;
private byte[] requestSignature;
private byte[] timewindow;
public SerializableCommitContainer(byte[] inputStates, byte[] txID, byte[] callerIdentity,
byte[] requestSignature, #Nullable byte[] timewindow) {
this.inputStates = inputStates;
this.txID = txID;
this.callerIdentity = callerIdentity;
this.requestSignature = requestSignature;
this.timewindow = timewindow;
}
public SerializableCommitContainer(List<StateRef> inputStates, SecureHash txID, Party callerIdentity,
NotarisationRequestSignature requestSignature,
#Nullable TimeWindow timeWindow){
this.inputStates = CustomUniquenessProvider.serialize((Serializable)inputStates);
this.txID = CustomUniquenessProvider.serialize(txID);
this.callerIdentity = CustomUniquenessProvider.serialize(callerIdentity);
this.requestSignature = CustomUniquenessProvider.serialize(requestSignature);
this.timewindow = CustomUniquenessProvider.serialize(timeWindow);
}
public byte[] getInputStates() {
return inputStates;
}
}
I have implemented net.corda.core.serialization.SerializationWhitelist
class Whitelist : SerializationWhitelist {
override val whitelist:List<Class<*>> =
listOf(SerializableCommitContainer::class.java)
}
My Notary implementation
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.ServiceHub
import net.corda.node.services.transactions.NonValidatingNotaryFlow
import java.security.PublicKey
class CustomNotary(override val services: ServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider: CustomUniquenessProvider = CustomUniquenessProvider()
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this)
override fun start() {
}
override fun stop() {
}
}
My UniquenessProvider
import net.corda.core.contracts.StateRef;
import net.corda.core.contracts.TimeWindow;
import net.corda.core.crypto.SecureHash;
import net.corda.core.flows.NotarisationRequestSignature;
import net.corda.core.identity.Party;
import net.corda.core.internal.notary.UniquenessProvider;
import java.io.Serializable;
import java.util.List;
public class CustomUniquenessProvider implements UniquenessProvider {
private static final Serializable notaryIdentity = "notaryIdentity";
public CustomUniquenessProvider(){
}
#Override
public void commit(List<StateRef> states, SecureHash txId, Party callerIdentity, NotarisationRequestSignature requestSignature, TimeWindow timeWindow) {
SerializableCommitContainer nextCommit = new SerializableCommitContainer(states, txId, callerIdentity, requestSignature, timeWindow);
byte[] serial = serialize(nextCommit);
Serializable deserial = deserialize(serial);
if(deserial == null){
System.out.println("CC DESERIALIZATION FAILED\n");
}
}
public static byte[] serialize(final Serializable serializable)
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream stream;
try {
stream = new ObjectOutputStream(bytes);
stream.writeObject(serializable);
stream.close();
byte[] ba = bytes.toByteArray();
bytes.close();
return ba;
} catch (IOException e) {
return null;
}
}
public static Serializable deserialize(final byte[] byteArray){
try {
ois = new ObjectInputStream(bais);
Serializable sp = (Serializable)stream.readObject();
stream.close();
return sp;
} catch (Throwable e) {
return null;
}
}
As of Corda 3.2, Java serialisation is disabled on Corda nodes. You need to use Corda's built-in serialisation engine instead.
You should start by basing your notary on an example from the Corda codebase, such as PersistentUniquenessProvider.

pagination of dynamodb using java in my project

I am trying to implement pagination of dynamodb using java in my project, i have implemented it, I am struggling in how to get the no. of elements in result per page using DynamoDBScanExpression. Can anyone help. Thanks
Here is my code
package com.morrisons.extendedrange.dao;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperConfig.TableNameOverride;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
import com.amazonaws.services.dynamodbv2.datamodeling.PaginatedScanList;
import com.amazonaws.services.dynamodbv2.datamodeling.QueryResultPage;
import com.amazonaws.services.dynamodbv2.datamodeling.ScanResultPage;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.amazonaws.services.dynamodbv2.model.ReturnConsumedCapacity;
import com.google.inject.Inject;
import com.morrisons.extendedrange.config.ExtendedRangeServiceConfiguration;
import com.morrisons.extendedrange.entity.ExtendedRangeEntity;
import com.morrisons.extendedrange.exception.ErrorCodes;
import com.morrisons.extendedrange.exception.ExtendedRangeServiceException;
import com.morrisons.extendedrange.model.ExtendedRange;
import com.morrisons.extendedrange.util.Logger;
import com.morrisons.extendedrange.util.LoggerFactory;
/**
* The Class ExtendedRangeDao.
*/
public class ExtendedRangeDao {
private static Logger LOGGER;
#Inject
private LoggerFactory logFactory;
Map<String, AttributeValue> lastEvaluatedKey = null;
#Inject
private void init() {
LOGGER = logFactory.getLogger(ExtendedRangeDao.class);
}
/** The range service configuration. */
#Inject
private ExtendedRangeServiceConfiguration extendedRangeServiceConfiguration;
/**
* Gets the range.
*
* #param storeId
* the store id
* #param catalogId
* the catalog id
* #return the range
*/
public ExtendedRange getExtendedRange(String catalogId, String productId, String page) {
LOGGER.debug("ExtendedRangeDao : getExtendedRange start ");
List<ExtendedRange> extendedRangeList = new ArrayList<ExtendedRange>();
try {
AmazonDynamoDBClient client = extendedRangeServiceConfiguration.getDynamoDBClient();
DynamoDBMapper mapper = new DynamoDBMapper(client);
Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
eav.put(":val1", new AttributeValue().withS(productId));
eav.put(":val2", new AttributeValue().withS(catalogId));
DynamoDBScanExpression scanExpression=new DynamoDBScanExpression()
.withFilterExpression("(productname =:val1 or productid =:val1) and catalogid = :val2 ")
.withExpressionAttributeValues(eav)
.withConsistentRead(true)
.withLimit(5)
.withExclusiveStartKey(lastEvaluatedKey) ;
ScanResultPage<ExtendedRangeEntity> queryResultPage = mapper.scanPage(ExtendedRangeEntity.class,
scanExpression, getDBMapperConfigForFetch());
List<ExtendedRangeEntity> extendedRangeEntityList = queryResultPage.getResults();
lastEvaluatedKey = queryResultPage.getLastEvaluatedKey();
if (extendedRangeEntityList.isEmpty()) {
String errorMessage = "No Record found or Multiple data found";
LOGGER.debug(errorMessage);
throw new ExtendedRangeServiceException(ErrorCodes.NO_RECORDS_FOUND, "ExtendedRangeEntity not found");
} else {
for (ExtendedRangeEntity extendedRangeEntity : extendedRangeEntityList) {
ExtendedRange extendedRange = new ExtendedRange();
copyProperties(extendedRange, extendedRangeEntity);
extendedRangeList.add(extendedRange);
LOGGER.debug("ExtendedRangeDao : getExtendedRange end ");
}
ExtendedRange returnExtendedRange = new ExtendedRange();
returnExtendedRange.setExtendedRangeList(extendedRangeList);
return returnExtendedRange;
}
} catch (AmazonServiceException e) {
String errorMessage = "Error in retrieving Data in DynamoDB";
LOGGER.error(errorMessage, e);
throw new ExtendedRangeServiceException(ErrorCodes.AMAZON_SERVICE_ERROR, errorMessage);
}
}
private DynamoDBMapperConfig getDBMapperConfigForFetch() {
String tableName = extendedRangeServiceConfiguration.getTableNameConfig()
.getTableName(ExtendedRangeEntity.class);
TableNameOverride tableNameOverride = new TableNameOverride(tableName);
DynamoDBMapperConfig dbMapperConfig = new DynamoDBMapperConfig(tableNameOverride);
return dbMapperConfig;
}
public ExtendedRangeServiceConfiguration getExtendedRangeServiceConfiguration() {
return extendedRangeServiceConfiguration;
}
public void setRangeServiceConfiguration(ExtendedRangeServiceConfiguration extendedRangeServiceConfiguration) {
this.extendedRangeServiceConfiguration = extendedRangeServiceConfiguration;
}
private void copyProperties(ExtendedRange target, ExtendedRangeEntity src) {
target.setProductId(src.getProductId());
target.setLeadTimeUOM(src.getLeadTimeUOM());
target.setCatalogId(src.getCatalogId());
target.setProductDesc(src.getProductDesc());
target.setProductName(src.getProductName());
target.setLeadTime(src.getLeadTime());
target.setCanBeOrderedFromDate(src.getCanBeOrderedFromDate());
target.setCanBeOrderedToDate(src.getCanBeOrderedFromDate());
}
}
The size() method should provide the number of elements in a page.
queryResultPage.getResults().size()

How to persist sorting order of jfx table column

This is regarding JFX Table.
I read above link and that was good for my work.
My requirement is - Suppose i click on a table column header and data is sorted in ascending or descending order. I want to persist that sorted data on my table even after restarting my application. Can someone please help me on this issue ? How can i remember the column header name and ascending / descending order and sort it at the time of initialization ?
Sorting order of jfx table clumn
You could simply store the necessary to restore the sorting in a file in your user directory. The following code does this by storing a int for the number of columns to sort by followed by an int and a TableColumn.SortType for each column. The int denotes the initial column index and the SortType specifies, whether the sorting is ascending or descending.
Example
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
public class SaveAndRestore extends Application {
private static final Path CONFIG_FILE;
static {
CONFIG_FILE = Paths.get(System.getProperty("user.home"), "myapp", "config.ser");
Path dir = CONFIG_FILE.getParent();
if (!Files.exists(dir)) {
try {
Files.createDirectory(dir);
} catch (IOException ex) {
throw new IllegalStateException("Could not create settings directory.", ex);
}
}
}
private static final InputStream getConfigReadStream() throws IOException {
return Files.exists(CONFIG_FILE) ? Files.newInputStream(CONFIG_FILE) : null;
}
private static final OutputStream getConfigWriteStream() throws IOException {
return Files.newOutputStream(CONFIG_FILE);
}
#Override
public void stop() throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(getConfigWriteStream())) {
oos.writeInt(tableView.getSortOrder().size());
for (TableColumn tc : tableView.getSortOrder()) {
oos.writeInt((Integer) tc.getUserData());
oos.writeObject(tc.getSortType());
}
}
}
public static class Item {
private final IntegerProperty number = new SimpleIntegerProperty();
private final StringProperty string = new SimpleStringProperty();
public Item(int number, String string) {
this.number.set(number);
this.string.set(string);
}
public final int getNumber() {
return this.number.get();
}
public final void setNumber(int value) {
this.number.set(value);
}
public final IntegerProperty numberProperty() {
return this.number;
}
public final String getString() {
return this.string.get();
}
public final void setString(String value) {
this.string.set(value);
}
public final StringProperty stringProperty() {
return this.string;
}
}
private static <T> TableColumn<Item, T> createColumn(String property) {
TableColumn<Item, T> column = new TableColumn<>(property);
column.setCellValueFactory(new PropertyValueFactory(property));
return column;
}
private TableView<Item> tableView;
#Override
public void start(Stage primaryStage) throws IOException, ClassNotFoundException {
tableView = new TableView<>(FXCollections.observableArrayList(
new Item(10, "Hello World"),
new Item(5, "Zyzz"),
new Item(20, "Aaron")
));
tableView.getColumns().addAll(createColumn("number"));
tableView.getColumns().addAll(createColumn("string"));
for (int i = 0, size = tableView.getColumns().size(); i < size; i++) {
tableView.getColumns().get(i).setUserData(i);
}
// restore state from config
InputStream is = getConfigReadStream();
if (is != null) {
try (ObjectInputStream ois = new ObjectInputStream(is)) {
for (int num = ois.readInt(); num > 0; num--) {
TableColumn<Item, ?> column = tableView.getColumns().get(ois.readInt());
column.setSortType((TableColumn.SortType) ois.readObject());
tableView.getSortOrder().add(column);
}
}
}
Scene scene = new Scene(tableView);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JAXB Marshal and Unmarshal Map to/from <key>value</key>

I'm trying to marshal and unmarshal Map to/from value pairs. I can marshal the object successfully, however, I cannot unmarshal it from the xml. The unmarshal result is the key exist in the Map, however, its value is null.
Here's the model I want to marshal and unmarshal:
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlRootElement(name="TestModel")
#XmlAccessorType(XmlAccessType.FIELD)
public class TestModel {
#XmlElement(name="Name")
private String name;
#XmlJavaTypeAdapter(MapAdapter.class)
private Map<String, String> metadata;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
}
I create a Map Adapter for marshal and unmarshal like below:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlAnyElement;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;
import org.w3c.dom.Element;
public class MapAdapter extends XmlAdapter<MapWrapper, Map<String, String>> {
#Override
public MapWrapper marshal(Map<String, String> m) throws Exception {
MapWrapper wrapper = new MapWrapper();
List<JAXBElement<String>> elements = new ArrayList<JAXBElement<String>>();
if (m != null && !m.isEmpty()) {
for (Entry<String, String> property : m.entrySet()) {
elements.add(new JAXBElement<String>(new QName(property.getKey()),
String.class, property.getValue().toString()));
}
}
wrapper.elements = elements;
return wrapper;
}
#Override
public Map<String, String> unmarshal(MapWrapper v) throws Exception {
Map<String, String> map = new HashMap<String, String>();
if (v != null && v.elements != null && !v.elements.isEmpty()) {
for (Object object : v.elements) {
Element element = (Element) object;
map.put(element.getNodeName(), element.getNodeValue());
}
}
return map;
}
}
class MapWrapper {
#XmlAnyElement(lax=true)
protected List<JAXBElement<String>> elements;
}
And the below class can test the marshal and unmarshal of the above model:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
public class TestEntry {
public static void main(String[] args) throws JAXBException {
testMarshal();
testUnmarshal();
}
public static void testMarshal() throws JAXBException {
Map<String, String> metadata = new HashMap<String, String>();
metadata.put("category", "test");
metadata.put("creation", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
TestModel model = new TestModel();
model.setMetadata(metadata);
model.setName("TESTMODEL");
marshal(model, System.out);
}
public static void testUnmarshal() throws JAXBException {
String model = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>"
+ "<TestModel>"
+ "<Name>TESTMODEL</Name>"
+ "<metadata>"
+ "<category>test</category>"
+ "<creation>2015-09-29</creation>"
+ "</metadata>"
+ "</TestModel>";
TestModel result = unmarshal(new ByteArrayInputStream(model.getBytes()), TestModel.class);
System.out.println("name=" + result.getName());
for (String key : result.getMetadata().keySet()) {
System.out.println(key + ", " + result.getMetadata().get(key));
}
}
public static <T> void marshal(T cls, OutputStream os) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(cls.getClass());
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(cls, os);
}
#SuppressWarnings("unchecked")
public static <T> T unmarshal(InputStream is, Class<T> cls)
throws JAXBException {
JAXBContext context = JAXBContext.newInstance(cls);
Unmarshaller unmarshaller = context.createUnmarshaller();
return (T) unmarshaller.unmarshal(is);
}
}
It seems that, the map only contains the key with 'null' value when invoke 'public Map unmarshal(MapWrapper v) throws Exception'.
And I also find a similar question about this issue "JAXB: how to marshall map into <key>value</key>", however, it didn't solve the unmarshal issue?
A very small correction:
map.put(element.getNodeName(), element.getTextContent());

Resources