We are having a SerializationException error when sending a list of objects using RPC and Java Generics.
I'm creating this widget to show the error:
public class Test<T> {
ListDataProvider<T> ldp = new ListDataProvider<T>();
public void setItems(List<T> list){
for(T t :list){
ldp.getList().add(t);
}
}
public List<T> getItems(){
return ldp.getList();
}
}
This is the code for creating the Test widget and passing a list of POJOs (where ExporterFormKey is the POJO object)
List<ExporterFormKey> list = new ArrayList<ExporterFormKey>();
ExporterFormKey key = new ExporterFormKey();
key.setKey("key1");
list.add(key);
Test<ExporterFormKey> test = new Test<ExporterFormKey>();
test.setItems(list);
At the end the next code throws a SerializationException:
service.sendList(test.getList(), new AsyncCallback...);
While the next one does fine:
service.sendList(list, new AsyncCallback...);
-----Edit----
I found that doing the next code also works
List<ExporterFormKey> newList = new ArrayList<ExporterFormKey>();
newList.add(test.getItems().get(0));
service.sendList(newList , new AsyncCallback...);
Or this also works
List<ExporterFormKey> newList = new ArrayList<ExporterFormKey>(test.getItems());
I also found this change on Test works!
public List<T> getItems(){
return new ArrayList<T>(ldp.getList());
}
http://blog.rubiconred.com/2011/04/gwt-serializationexception-on-rpc-call.html
As izaera suggested the ListDataProvider uses a non-serializable list implementation (ListWrapper) which cannot be sent directly across the wire.
Wrapping the response from ListDataProvider's getList() method into a new ArrayList as you have suggested in your post is the simplest way to workaround the issue.
Related
I am new to Hazelcast jet and in my application on data I am doing some aggregation and getting data but I want to send that in rest response so how can I change it to normal list?
public class ResponseMessage<T> {
private T responseClassType;
private ResponseMessage() {}
private ResponseMessage(T t) {
this.responseClassType = t;
}
public static <T> ResponseMessage<T> withResponseData(T classType) {
return new ResponseMessage<T>(classType);
}
public static ResponseMessage<Void> empty() {
return new ResponseMessage<>();
}
public T getResponseClassType() {
return responseClassType;
}
public void setResponseClassType(T responseClassType) {
this.responseClassType = responseClassType;
}
}
This is my generic response class and as below I am sending response after all calculations:
public ResponseMessage<?> runProcess(Pipeline pl) {
Map<String, BatchStage<Object>> allBatch = new HashMap<String,BatchStage<Object>>();
allBatch.put(z.get("id").toString(), new SomeCalulation().readSource(pipeline));
BatchStage<Object> h = allBatch.values().iterator().next();
h.writeTo(Sinks.list("abc"));
IList<Object> abc = jetInstance.getList("abc");
List<Object> result = new ArrayList(abc);
abc.destroy();
return ResponseMessage.withResponseData(result);
}
Now this is working but everytime I call rest request it is increasing the list and if I clear the list it is showing blank records, please help how can I convert it to normal list or best way to send response?
It was not working because I was joining it after method call:
runProcess(pl);
job.join(); // so because I am joining it after runProcess not working but if I directly return ResponseMessage.withResponseData(jetInstance.getList("abc")); and then join it will work.
I don't see submitting the pipeline as a job and waiting for the result (job.join()). I suppose you have omitted this from your code sample.
To solve your issue with empty list simply copy the result before destroying the list:
job.join();
IList<Object> abc = jetInstance.getList("abc");
List<Object> result = new ArrayList(abc)
abc.destroy();
return ResponseMessage.withResponseData(result);
Also, the list should have a unique name for each request, otherwise, multiple requests will write to the same list, having unpredictable results.
I have List like below code snippet:
List<String> nameList = new ArrayList<>();
nameList.add("Robert");
nameList.add("Tom");
nameList.add("Curran");
//robert, tom, curran
Now I want to modify these list values using reflection API in Java. Output something like below if we print list:
//xxx,xxx,xxx
You can achieve this using Java reflection as follows:
The class that contains nameList:
public class SampleClass {
List<String> nameList;
}
This is a simple test method using SampleClass:
#Test
public void testReflection() throws NoSuchFieldException, IllegalAccessException {
SampleClass sample= new SampleClass();
sample.nameList = new ArrayList<>();
sample.nameList.add("Robert");
sample.nameList.add("Tom");
sample.nameList.add("Curran");
Field fieldList = SampleClass.class.getDeclaredField("nameList");
fieldList.setAccessible(true);
List<String> listToModify = (List<String>) fieldList.get(sample);
for (int i =0;i<listToModify.size();i++) {
if(listToModify.get(i).equals("Robert"))
listToModify.set(i, "xxxxxx");
};
System.out.println(sample.nameList.get(0));
System.out.println(sample.nameList.get(1));
System.out.println(sample.nameList.get(2));
}
Output:
xxxxxx
Tom
Curran
NOTE: I don't recommend using setAccessible(true), it destroys OOP encapsulation.
You can add the custom annotation concept to solve this problem. By creating custom annotation you can simply modify those fields only whichever is required. Fields that need to be modified will be annotated with custom annotation and the rest are ignored. For reference: Java reflection.
As the title says how does the map from org.springframework.data.domain.Page work?
As per the documentation, it suggests that you put a converter as a map parameter, and you get a new map with the converted contents. As per this interpretation, I wrote code to something as follows.
Page<T> results = getPagedResults();
return results.map(x -> {
x.setElement("some constant");
return x;
});
However the above did not work as expected. I got the Page with all the other values intact like count and so on, but the content was an empty list! I actually had to write code this way.
Page<T> results = getPagedResults();
return new Page<T>() {
#Override
public int getTotalPages() {
return results.getTotalPages();
}
...
#Override
public List<T> getContent() {
List<T> contents = result.getContent();
for (T content : contents) {
content.setElement("some constant");
}
return contents;
}
...
};
The second choice is verbose and seems to do redundant work. I would have expected the first piece of code to do the same, but it did not.
Am I reading the documentation wrong? And where would you use the map function of Page if it is not supposed to be used, the way I was using it?
How about putting a real converter like below:
Page<T> results = getPagedResults();
Page<T> convertedResults = results.map(new Converter<T, T>() {
#Override
public T convert(T page) {
Page page2 = new Page();
page2.setElement('some constant');
//set other fields
return page2;
}
});
You can use map the same way as we do:
results.map(mapper::convert)
where the convert is the method of mapper to convert database entity to DTO or any another object.
Supposed I have a User list on Post entity
private List<User> users = new ArrayList<>();
and I can clear it using
post.getUsers().clear();
and can add to it with
post.getUsers().addAll(Something);
how can I do the same if use to call the function getUsers dynamically? I tried
post.getClass().getMethod("getUsers").invoke(post).getClass().getMethod("clear").invoke(new ArrayList<>());
and also I tried
ArrayList.class.getMethod("clear").invoke(post);
but im getting a
WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver - Resolved [java.lang.IllegalArgumentException: object is not an instance of declaring class]
Any idea on how can I do it?
take note that this is working
post.getClass().getMethod("getUsers").invoke(post); //get the users
I just have no idea how can I chained the clear method or the addAll on it.
Method.invoke returns an Object and the Object class does not have a clear() method, so you need to cast the returned object into a List.
Post post = new Post();
Method m = post.getClass().getMethod("getUsers");
List<User> users = (List<User>)m.invoke(post);
users.clear();
And if you want it in one line, it is ugly, but can be done:
((List<User>)(post.getClass().getMethod("getUsers").invoke(post))).clear();
This also works:
List.class.cast(post.getClass().getMethod("getUsers").invoke(post)).clear();
Just check the JavaDocs of ArrayList.
ArrayList.clear() returns nothing because it's a void method, ArrayList.addAll() returns a boolean.
So to allow for method chaining, you'd have to wrap the calls in your Post entity to return the post entity itself. Then you could get method chaining.
So, in your Post entity, you'd add:
public Post clearUsers() {
users.clear();
return this;
}
and
public Post addAllUsers(List<User> usersToAdd) {
users.addAll(usersToAdd);
return this;
}
and invoke these methods instead of getUsers().xyz() (which is very bad style anyway. The posobject owns these users. it should be the only one to manipulate these).
You need to invoke those methods on the array list object contained by post.
For example, you can call clear on post.getUsers() as
ArrayList.class.getMethod("clear").invoke(
// this will give you reference to array list contained by post
post.getClass().getMethod("getUsers").invoke(post)
);
Tested above code as following
public class DynamicInvocation {
public static void main(String[] args) throws Exception {
Post post = new Post();
post.getUsers().add(new User());
System.out.println(post.getUsers());
ArrayList.class.getMethod("clear").invoke(
post.getClass().getMethod("getUsers").invoke(post)
);
System.out.println(post.getUsers());
}
}
class Post {
private List<User> users = new ArrayList<>();
public List<User> getUsers() {
return users;
}
}
class User {
}
I'd like to have a firestore Document with several hashmaps as data objects on the server-side.
On the client-side (Android) I am using a POJO as a custom object to store and organize all data requested from firestore. This POJO implements Parcelable to be able to pass it through intent from an activity to another.
My POJO looks like this:
public class Session implements Parcelable {
private Map<String, Boolean> firstHashmap;
private Map<String, Boolean> secondHashmap;
private Map<String, String> thirdHashmap;
// constructors (first I notice that I can only create 1 constructor for a hashmap as a parameter, I wonder why)
// getters and setters
#Override
public int describeContents() {
return 0;
}
// Write the object's data to the passed-in Parcel
#Override
public void writeToParcel(Parcel out, int flags) {
if (firstHashmap != null) {
Bundle firstBundle = new Bundle();
for (Map.Entry<String, Boolean> entry : firstHashmap.entrySet()) {
firstBundle.putBoolean(entry.getKey(), entry.getValue());
}
out.writeBundle(firstBundle);
}
// same thing for secondHashmap
// same thing for thirdHashmap
public static final Parcelable.Creator<Session> CREATOR = new
Parcelable.Creator<Session>() {
public Session createFromParcel(Parcel in){
return new Session(in);
}
#Override
public Session[] newArray(int size) {
return new Session[size];
}
};
// Constructor that takes a Parcel and gives you an object populated with it's values
private Session(Parcel in) {
Bundle firstBundle = new Bundle();
firstBundle = in.readBundle(getClass().getClassLoader());
Map<String, Boolean> tempFirstHashmap = new HashMap<>();
for (String key: firstBundle.keySet()){
tempFirstHashmap.put(key, firstBundle.getBoolean(key));
}
firstHashmap = firstBundle;
// same thing for secondHashmap
// same thing for thirdHashmap
}
}
It worked fine while I was having the same value data type in my hashmap i.e. Boolean but then I introduced thirdHashmap with String and everything went wrong: for instance when firstHashmap is null on the firestore document and thirdHashmap is not, I'm having Strings deserialized to my firstHashmap causing RuntimeError.
To sum up I am having some issue to get data from firestore and put them in the right place. If anyone could give me a clue it would be much appreciated :)
Thank you very much!
To save you a lot of time and effort you can use a plugin to generate parceling code for you inside your POJO if you're using android studio just go to File>settings>plugins and there search for plugins and install it.
then go to your POJO and clear it from everything except your member variable(HashMap variables) after that just by right clicking your mouse on inside your class file and from your opened window click on generate you will find a new tab added to the last of the window called Parcelable click on it and choose what member variables you want to parcel.
Note: If i were in your shoes i would delete all my firestore data on server-side and on client-side when a new change made to my POJO specially when I delete or change member variable, I hope this answer help you.