Having trouble with BIRT 4.3 new POJO DataSource - java

I am following the example at http://www.eclipse.org/birt/phoenix/project/notable4.3.php#jump_3
and I can't seem to get it to work properly. At the step where you define the new DataSet (New Birt POJO Data Set), I can't seem to find the 'POJO Data Set Class Name'. The matching item widget remains empty. I tried editing the rptdesign with the source tab trying all kind of variations (with/without package name), nothing does it. Anybody had success with this new feature of BIRT ?

Ok, my bad. It would have been easier if we had to implement an interface rather than trying to deduce how birt reads a custom pojo dataset.
So on the example at http://www.eclipse.org/birt/phoenix/project/notable4.3.php#jump_3 everything worked as described except for the class CustomerDataSet. Here is the implementation of the class CustomerDataSet that worked for me.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class CustomerDataSet {
public Iterator<Customer> itr;
public List<Customer> getCustomers() {
List<Customer> customers = new ArrayList<Customer>();
Customer c = new Customer(103);
c.setCity("City1");
c.setCountry("Country1");
c.setCreditLimit(100);
c.setName("aName1");
c.setState("state1");
customers.add(c);
c = new Customer(104);
c.setCity("City2");
c.setCountry("Country2");
c.setCreditLimit(200);
c.setName("aName2");
c.setState("aStat2");
customers.add(c);
c = new Customer(105);
c.setCity("City3");
c.setCountry("Country3");
c.setCreditLimit(300);
c.setName("aName3");
c.setState("aStat3");
customers.add(c);
return customers;
}
public void open(Object obj, Map<String,Object> map) {
}
public Object next() {
if (itr == null)
itr = getCustomers().iterator();
if (itr.hasNext())
return itr.next();
return null;
}
public void close() {
}
}

Related

Use map or list to store objects that should be accessed by object’s property?

I want to implement a book repository where books can be added, removed and updated. Books in this repository should be accessed by their ISBN which is an object property.
Should I use a list or a map? If a map is to be used, how do I get the key and value’s property to remain in sync?
You can use an arraylist like this:
ArrayList<Book> myBooks = new ArrayList<Book>();
After you add the book objects you can easily retrieve them by their properties
Example:
for(int i=0; i < myBooks.length; i++){
if(myBooks[i].getISBN().equals("Specific ISBN that you want to look for")){
// enter code here
}
}
Please note that you need the getter method for the ISBN number in the Book class
Hope it helps. Good luck!
A map seems like a way to go because it will take care of collection search for you. If you will look for books by isbn, I would suggest using a Map because its search has O(1) performance, while searching through a List has O(n) meaning that for large number of books, a Map will perform better.
On the other hand if you, for some reason, go with the List implementation (maybe you need linear data representation, or you need to know the order in which Book objects where added) then I would suggest that you also look in to the Set Collection implementation because it will help you deal with duplicates.
Also, please keep in mind that this will store data temporally, meaning that all data will be lost when the program terminates. If you need your data to stay intact you should look into persisting data with Java - storing data to files, having a data base or even sending it a Web servise that will keep you data.
The most flexible solution is to have a peace of code that handles the logic of selecting what storage type you (or your user) need. That could be achieved by using an interface that has all the methods you need (like read, update, delete) and separate classes that implement that interface.
Below is an example Interface and 2 implementations, one for Map and one for a database(mock only)
Interface:
public interface IBookRepository{
boolean addBook(Book book);
void removeBook(Book book);
void removeBookWithIsbn(String isbn);
void editBook(Book bookWithNewProperties);
void editBookTitle(String isbn, String title);
}
Map implementation:
public MapBookRepository() implements IBookRepository{
private HashMap<String, Book> mBookMap;
public MapBookRepository(){
this.mBookMap = new HashMap<String, Book>();
}
public boolean addBook(Book book){
if(!mBookMap.containsKey(book.isbn)){
return mBookMap.put(book.isbn, book);
}
return false;
}
public void removeBook(Book book){
RemoveBookWithIsbn(book.isbn);
}
public void removeBookWithIsbn(String isbn){
if(mBookMap.containsKey(book.isbn)){
remove(book.isbn);
}
}
public void editBook(Book bookWithNewProperties){
removeBookWithIsbn(bookWithNewProperties.isbn);
addBook(bookWithNewProperties);
}
public void editBookTitle(String isbn, String title){
if(mBookMap.containsKey(isbn)){
mBookMap.get(isbn).setTitle(title); // You need a setter in your Book Class
}
}
}
Data base implementation (only mock..):
public DbBookRepository() implements IBookRepository{
public DbBookRepository(){
// Db stuff
}
public boolean addBook(Book book){
// Db stuff
}
public void removeBook(Book book){
// Db stuff
}
public void removeBookWithIsbn(String isbn){
// Db stuff
}
public void editBook(Book bookWithNewProperties){
// Db stuff
}
public void editBookTitle(String isbn, String title){
// Db stuff
}
}
Use example could be:
public static void main(String [] args)
{
if(args.length != 1){
return;
}
IBookRepository repo;
if(args[0].equal("DB")){
repo = new DbBookRepository();
}else if (args[0].equal("Map")){
repo = new MapBookRepository();
}else{
return;
}
repo.addBook(new Book("a", "b", "c", 1.0));
}
A Map would help like this
Map<String, Book> bookMap = new HashMap<String, Book>();
bookMap.put(book1.getIsbn(), book1);
bookMap.put(book2.getIsbn(), book2);
bookMap.put(book3.getIsbn(), book3);
bookMap.put(book4.getIsbn(), book4);
Give the key get the Book.
you can use getter and setter method in your pojo class to achieve this.
ArrayList<Book> books=new ArrayList<Book>(0);
For insertion
books.add(book1);
Here book1 is a Book class object
For Updation
book1.getBookName(book1.setBookName("name"));
For Deletion
books.remove(1);
There remove takes the index value of book object
I'm going to use an HashMap in the book repository. When I want to add a book to the repository, I'm going to add a copy of that book instead of a reference, so it cannot be modified outside of the repository. The same applies to to updating and getting a book.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BookRepository {
private Map<String, Book> books;
public BookRepository() {
this.books = new HashMap<>();
}
public Book get(String isbn) {
Book copy = null;
if (this.books.containsKey(isbn))
copy = new Book(this.books.get(isbn));
return copy;
}
public boolean add(Book book) {
Book copy = new Book(book);
return this.books.putIfAbsent(copy.getIsbn(), copy) == null;
}
public boolean remove(String isbn) {
return this.books.remove(isbn) != null;
}
public boolean update(Book book) {
Book copy = new Book(book);
return this.books.computeIfPresent(copy.getIsbn(), (isbn, existing) -> copy) != null;
}
public List<Book> getAll() {
List<Book> list = new ArrayList<>();
for (Book book : this.books.values())
list.add(new Book(book));
return list;
}
public void removeAll() {
this.books.clear();
}
}

Iterate through an ArrayList + Observer Pattern

I have recently tried programming various versions of Iterator and Observer Patterns in Java, but currently I'm stuck at the implementation of both in one project:
What I'm trying to accomplish is to iterate through an ArrayList, which contains elements of type and after each iteration-process to inform the Observer about the change and the containing element.
Also I want to be able to choose the "type" of iterator in my separate classes
Edit: I finally finished the project, hopefully this is the correct version of a "Observer Iterator", any tips on how to improve the code are greatly appreciated, but thanks to everybodys' encouraging tips.
Code:
First of all, the helper class for the Observer interface, which implements the update method. Changes with every iteration process and prints out the element.
package iteratorObserver;
import java.util.List;
public class ConcreteObserver implements Observer{
#Override
public void update(String element) {
System.out.println("The current element is: " + element);
}
}
The actual Observer interface:
package iteratorObserver;
public interface Observer<E> {
void update(String element);
}
Following that, the same by using a ConcreteSubject and an abstract class Subject
The observer pattern requires the getState and setState, so I integrated both into the next method. Getting the element by iterator.next aswell as setting it.
package iteratorObserver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ConcreteSubject<E> extends Subject implements IteratorStandardFilter<E>{
List<String> listElements = new ArrayList<String>();
private int index;
private Iterator<E> iterator;
public ConcreteSubject(Iterator<E> iterator, List<String> listElements){
index = 0;
this.iterator = iterator;
this.listElements = listElements;
}
public void addElement(String element) {
listElements.add(element);
notifyObservers(element);
}
// check the size of the elements list and return if there are still
// elements left out
#Override
public boolean hasNext() {
if ((listElements.size() == index))
return false;
return true;
}
// iterate to the next element, not sure if it's overridden, since the
// actual next method of the iterator is still in use
#Override
public E next() {
E result = iterator.next();
index++;
notifyObservers((String) result);
return result;
}
}
Subject class, only used for the standard observer methods
package iteratorObserver;
import java.util.ArrayList;
import java.util.List;
public class Subject {
public List<Observer> listObservers = new ArrayList<Observer>();
public void addObserver(Observer observer){
listObservers.add(observer);
}
public void removeObserver(Observer observer){
listObservers.remove(observer);
}
public <E> void notifyObservers(String element){
for(Observer observerHelp : listObservers){
observerHelp.update(element);
}
}
}
Since i want to add various filters, not only the standard list-iterator, i added a class IteratorStandardFilter, as you can see here:
package iteratorObserver;
import java.util.Iterator;
public interface IteratorStandardFilter<E> extends Iterator<E> {
public boolean hasNext();
public E next();
}
And finally, the Test method, the main:
package iteratorObserver;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class iteratorObserverMain {
public static void main(String [] args){
List<String> listElements = new ArrayList<String>();
listElements.add("hello");
listElements.add("there");
listElements.add("everybody");
listElements.add("it");
listElements.add("works");
listElements.add("Thanks for your help. Nice and encouraging forum");
Iterator<String> iterator = listElements.iterator();
ConcreteSubject<String> concreteSubject = new ConcreteSubject<String>(iterator, listElements);
concreteSubject.addObserver(new ConcreteObserver());
while(concreteSubject.hasNext()){
concreteSubject.next();
}
}
}

Get all the table items from DynamoDB table using Java High Level API

I implemented scan operation using in dynamodb table using dynamodbmapper, but I'm not getting all the results. Scan returns different number of items, whenever I run my program.
Code snippet :
DyanmoDBScanExpression scanExpression = new DynamoDBScanExpression();
List<Books> scanResult = mapper.scan(Books.class, scanExpression);
I investigated into it, and found out about the limit of the items scan returns. But I couln't find a way to get all the items from the table using mapper! Is there a way so I can loop through all the items of the table. I have set enough heap memory in JVM so there won't be memory issues.
In java use the DynamoDBScanExpression without any filter,
// Change to your Table_Name (you can load dynamically from lambda env as well)
DynamoDBMapperConfig mapperConfig = new DynamoDBMapperConfig.Builder().withTableNameOverride(DynamoDBMapperConfig.TableNameOverride.withTableNameReplacement("Table_Name")).build();
DynamoDBMapper mapper = new DynamoDBMapper(client, mapperConfig);
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression();
// Change to your model class
List < ParticipantReport > scanResult = mapper.scan(ParticipantReport.class, scanExpression);
// Check the count and iterate the list and perform as desired.
scanResult.size();
the scan should return all the items.
the catch is that the collection returned is lazily loaded.
you need to iterate through the List and when it consumes all the items that are fetched additional calls will be made behind the scenes to bring in more items (until everything is brought in).
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/JavaQueryScanORMModelExample.html
In that example it's:
List<Book> scanResult = mapper.scan(Book.class, scanExpression);
for (Book book : scanResult) {
System.out.println(book);
}
You need to iterate until LastEvaluatedKey is no longer returned. Check how is done in one of the official examples from the SDK:
https://github.com/awslabs/aws-dynamodb-examples/blob/23837f36944f4166c56988452475edee99868166/src/main/java/com/amazonaws/codesamples/lowlevel/LowLevelQuery.java#L70
A little bit late, but
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
public final class LogFetcher {
static AmazonDynamoDBClient client = new AmazonDynamoDBClient();
static String tableName = "SystemLog";
public static List<SystemLog> findLogsForDeviceWithMacID(String macID) {
client.setRegion(Region.getRegion(Regions.EU_WEST_1));
DynamoDBMapper mapper = new DynamoDBMapper(client);
Map<String, AttributeValue> eav = new HashMap<String, AttributeValue>();
eav.put(":val1", new AttributeValue().withS(macID));
DynamoDBQueryExpression<SystemLog> queryExpression = new DynamoDBQueryExpression<SystemLog>()
.withKeyConditionExpression("parentKey = :val1")
.withExpressionAttributeValues(eav);
List<SystemLog> requestedLogs = mapper.query(SystemLog.class, queryExpression);
return requestedLogs;
}
}
And sample class
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
#DynamoDBTable(tableName="SystemLog")
public final class SystemLog {
public Integer pidValue;
public String uniqueId;
public String parentKey;
//DynamoDB
//Partition (hash) key
#DynamoDBHashKey(attributeName="parentKey")
public String getParentKey() { return parentKey; }
public void setParentKey(String parentKey) { this.parentKey = parentKey; }
//Range key
#DynamoDBRangeKey(attributeName="uniqueId")
public String getUniqueId() { return uniqueId; }
public void setUniqueId(String uniqueId) { this.uniqueId = uniqueId;}
#DynamoDBAttribute(attributeName="pidValue")
public Integer getPidValue() { return pidValue; }
public void setPidValue(Integer pidValue) { this.pidValue = pidValue; }
}
By default, the DynamoDBMapper#scan method returns a "lazy-loaded" collection. It
initially returns only one page of results, and then makes a service
call for the next page if needed. To obtain all the matching items,
iterate over the paginated results collection.
However, PaginatedScanList comes with out of box PaginatedScanList#loadAllResults method which helps to eagerly load all results for this list.
NOTE: loadAllResults method is not supported in ITERATION_ONLY mode.
List<Books> scanResult = mapper.scan(Books.class, new DynamoDBScanExpression());
scanResult.loadAllResults();//Eagerly loads all results for this list.
//Total results loaded into the list
System.out.println(scanResult.size());
DyanmoDBScanExpression scanExpression = new DynamoDBScanExpression();
List<Books> scanResult = new ArrayList<Books>(mapper.scan(Books.class, scanExpression));
This will work, it will iterate all the items and then returns a list.

Tricky ClassCastException in using the Plastic framework for code generation

What's it all about
Hi everyone - I was looking at a presentation called "Have your cake and eat it too: Meta-programming in Java"
The presenter was Howard M. Lewis Ship, (one?) of the authors of Tapestry - and in making that, a subproject called "plastic" was made to leverage ASM to alter the bytecode.
I won't pretend to be an expert, but the end-result should be that I can write code such that annotated classes, methods and fields can be used to generate further java code thus cutting down on boilerplate code.
My Question
The code below is a complete example to demonstrate my problem. The test example should modify the EqualsDemo class such that it contains implementations of equals() and hashCode().
When running it I get an error which basically states that I can't cast an object of type 'com.example.plastic.transformed.EqualsDemo' to 'com.example.plastic.transformed.EqualsDemo' (yes, the same class).
The presenter just mentioned that these errors were annoying without alluding to where they stem from - my searching so far indicates that they pertain to different class loaders.
I have, however, been completely unable to fix the problem, hence my question here(!)
com.example.plastic.transformed.EqualsDemo cannot be cast to com.example.plastic.transformed.EqualsDemo
at MainClass.main(MainClass.java:28)
So what do I need to do ? Replace class loaders ? (if so, how?) or is there some part of Plastic I don't get ? Some method for generating proxy objects or similar that I need to use for things to go smoothly ?
PS!
The examples I've found so far all use what I think is Groovy in the final use of the annotated instances.
Hopefully someone are more competent than I :)
Links:
Tapestry Homepage (plastic is contained as a jar in the download): http://tapestry.apache.org/
Main.java
import java.util.ArrayList;
import java.util.List;
import org.apache.tapestry5.internal.plastic.StandardDelegate;
import org.apache.tapestry5.plastic.ClassInstantiator;
import org.apache.tapestry5.plastic.PlasticManager;
import com.example.plastic.transformer.EqualsHashCodeTransformer;
import com.example.plastic.transformed.EqualsDemo;
public class MainClass {
public static void main(String[] args) {
List<String> pList = new ArrayList<String>();
pList.add("com.example.plastic.transformed");
PlasticManager pm = PlasticManager
.withContextClassLoader()
.delegate( new StandardDelegate(new EqualsHashCodeTransformer()) )
.packages(pList)
.create();
final String EQUALSDEMO = "com.example.plastic.transformed.EqualsDemo";
ClassInstantiator<EqualsDemo> i = pm.getClassInstantiator(EQUALSDEMO);
i.newInstance().hashCode();
/*
com.example.plastic.transformed.EqualsDemo cannot be cast to com.example.plastic.transformed.EqualsDemo
at MainClass.main(MainClass.java:28)
*/
}
}
ImplementEqualsHashCode.java
package com.example.plastic.annotations;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.*;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ImplementEqualsHashCode {
}
EqualsHashCodeTransformer.java
package com.example.plastic.transformer;
import java.util.ArrayList;
import java.util.List;
import org.apache.tapestry5.plastic.FieldHandle;
import org.apache.tapestry5.plastic.MethodAdvice;
import org.apache.tapestry5.plastic.MethodDescription;
import org.apache.tapestry5.plastic.MethodInvocation;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticClassTransformer;
import org.apache.tapestry5.plastic.PlasticField;
import com.example.plastic.annotations.*;
public class EqualsHashCodeTransformer implements PlasticClassTransformer {
private MethodDescription EQUALS = new MethodDescription("boolean", "equals", "java.lang.Object");
private MethodDescription HASHCODE = new MethodDescription("int", "hashCode");
private static final int PRIME = 37;
public void transform(PlasticClass plasticClass){
//check that the class is annotated
if(!plasticClass.hasAnnotation(ImplementEqualsHashCode.class)) {
return;
}
List<PlasticField> fields = plasticClass.getAllFields();
final List<FieldHandle> handles = new ArrayList<FieldHandle>();
for(PlasticField field : fields){
handles.add(field.getHandle());
}
//HashCode method introduction :)
plasticClass.introduceMethod(HASHCODE).addAdvice(new MethodAdvice() {
public void advise(MethodInvocation invocation){
Object instance = invocation.getInstance();
int result = 1;
for(FieldHandle handle : handles){
Object fieldValue = handle.get(instance);
if(fieldValue != null)
result = (result * PRIME) + fieldValue.hashCode();
}
invocation.setReturnValue(result);
//Don't proceed to the empty introduced method
}
});
plasticClass.introduceMethod(EQUALS).addAdvice(new MethodAdvice() {
public void advise(MethodInvocation invocation) {
Object thisInstance = invocation.getInstance();
Object otherInstance = invocation.getParameter(0);
invocation.setReturnValue(isEqual(thisInstance, otherInstance));
//Don't proceed to the empty introduced method
}
private boolean isEqual(Object thisInstance, Object otherInstance) {
if(thisInstance == otherInstance)
return true;
if(otherInstance == null)
return false;
if(!(thisInstance.getClass() == otherInstance.getClass()))
return false;
for(FieldHandle handle : handles){
Object thisValue = handle.get(thisInstance);
Object otherValue = handle.get(otherInstance);
if(!(thisValue == otherValue || thisValue.equals(otherValue)))
return false;
}
return true;
}
});
}
}
EqualsDemo.java
package com.example.plastic.transformed;
import com.example.plastic.annotations.ImplementEqualsHashCode;
#ImplementEqualsHashCode
public class EqualsDemo {
private int intValue;
private String stringValue;
public int getIntValue(){
return intValue;
}
public void setIntValue(int intValue){
this.intValue = intValue;
}
public String getStringValue(){
return stringValue;
}
public void setStringValue(String stringValue){
this.stringValue = stringValue;
}
}
you don't want to add the packages to the plastic manager -- it uses a different classloader and will load those classes, making two copies of the classes in those packages (one in the parent classloader and one in the plastic classloader) which will give you the ClassCastException you are seeing when the framework tries to cast to your class. Try this instead:
import org.apache.tapestry5.internal.plastic.StandardDelegate;
import org.apache.tapestry5.plastic.ClassInstantiator;
import org.apache.tapestry5.plastic.PlasticManager;
public class MainClass {
public static void main(String[] args) {
PlasticManager pm = PlasticManager
.withContextClassLoader()
.delegate(new StandardDelegate())
.create();
ClassInstantiator<EqualsDemo> ci = pm.createClass(EqualsDemo.class, new EqualsHashCodeTransformer());
System.out.println(ci.newInstance().hashCode());
}
}
I guess instead of
PlasticManager.withContextClassLoader()...
using the following should fix your problem:
PlasticManager.withClassLoader(getClass().getClassLoader())...

documents not removed using mongo-jackson

I'm chasing a severe bug in our application and after some digging I broke it down to this example. Using the jackson collection, I'm not able to remove documents. Usually it works for a single document which I just inserted. In the following example I just try to purge a unittest-collection.
import java.net.UnknownHostException;
import net.vz.mongodb.jackson.DBCursor;
import net.vz.mongodb.jackson.JacksonDBCollection;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.test.model.Account;
public class MongoTest {
public static void main(String[] args) {
try {
Mongo mongo = new Mongo("localhost", 27017);
DB db = mongo.getDB("orion-unittest");
// get a single collection
DBCollection collection = db.getCollection("account");
JacksonDBCollection<Account, String> jacksonCollection = JacksonDBCollection.wrap(collection, Account.class, String.class);
// doesn't work
DBCursor<Account> jacksonCursor = jacksonCollection.find();
while (jacksonCursor.hasNext()) {
jacksonCollection.remove(jacksonCursor.next());
}
System.out.println(jacksonCollection.getCount());
// works!
com.mongodb.DBCursor cursor = collection.find();
while (cursor.hasNext()) {
collection.remove(cursor.next());
}
System.out.println(collection.getCount());
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (MongoException e) {
e.printStackTrace();
}
}
}
Console output:
6
0
The strange part ist, when I monitor the mongo server activities I see the delete commands being passed. do I miss something, or is this a bug of the jackson mapper?
here the Account class - nothing much there. the id is filled with the _id key:
import net.vz.mongodb.jackson.Id;
public class Account {
#Id
String id;
String nickname = null;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}
I haven't used mongo-jackson-mapper but as I saw in the source code it only calls dbCollection.remove method itself. So I guess your problem is with WriteConcern. try this one :
jacksonCollection.remove(jacksonCursor.next(), WriteConcern.FSYNC_SAFE);
and test 2 deletes separately (not one after another), so you can really understand if its deleting or not (it can have delete lag as well).
I found the solution by using the #ObjectId instead of #Id. the problem was, that before my id was serialized to
{"_id": ObjectId("4f7551a74206b4f8e692bda3")}
and not to something like:
{"_id": "4f7551a74206b4f8e692bda3"}
... which failed the query done by remove trying to find the matching object. the #ObjectId annotation now equals the results of the serialization.

Categories

Resources