I'm trying a simple test where I try to commit an object of a Class that extends HashMap. I'm left with a MANAGED_CLASS_MAPPING_ERROR: given javaClass 'class com.vehco.Configuration' is mapped to MapType, expected ManagedType. Do Javers not support is-a but only has-a?
Been reading the documentation forwards and backwards but unable to find anything. Google was neither my friend this time around.
Please find the test code below:
Tester.java:
public class Tester {
Javers javers = JaversBuilder.javers().build();
public static void main(String[] args) {
Tester t = new Tester();
t.start();
}
private void start() {
Configuration data = new Configuration("global");
for (Integer i = 0; i < 10; i++) {
data.getProp().put(i.toString(), UUID.randomUUID().toString());
}
javers.commit("svenie",data);
for (Integer i = 0; i < 10; i++) {
if (i % 2 == 0)
data.getProp().put(i.toString(), UUID.randomUUID().toString());
}
javers.commit("svenie",data);
List<Shadow<Configuration>> changes = javers.findShadows(QueryBuilder.byClass(Configuration.class).build());
for (Shadow<Configuration> change : changes) {
System.out.println(change.getCommitMetadata());
}
}
}
Configuration.java:
public class Configuration extends HashMap<String,String> {
#Id
private String name;
private Properties prop = new Properties();
public Configuration(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Properties getProp() {
return prop;
}
}
Looks like the Javers type inferring is ambiguous here. On the one hand your class extends Map so JaVers can infer it as MapType, on the other hand it has #Id property, so JaVers can infer it as Entity. I think you have to decide which Javers type is better for your class and register this class explicitly using JaversBuilder.register*()?
A class can't be mapped to more than one Javers type.
Related
I'm trying to get the id of the last inserted object into a database using Room with Android. I can fetch the last object using an SQL query and can call other methods to get the various properties of that object which the user has set when saving the object. But getId() always returns 0. When I examine the table contents in Android Studio's app inspector, I can clearly see that Room is generating a unique primary key for each row, but I just can't get at it. Can anyone suggest what the problem might be?
Here's the Dao query:
#Query("SELECT * FROM gamebooks_table WHERE gamebookId=gamebookId ORDER BY gamebookId DESC LIMIT 1")
LiveData<Gamebook> getSingleGamebookByID();
And here's the annotated entity class:
#Entity(tableName = "gamebooks_table")
public class Gamebook {
#PrimaryKey(autoGenerate = true)
private long gamebookId;
private String gamebookName;
private String gamebookComment;
private String gamebookPublisher;
private float gamebookStarRating;
public Gamebook(String gamebookName, String gamebookComment, String gamebookPublisher, float gamebookStarRating) {
this.gamebookName = gamebookName;
this.gamebookComment = gamebookComment;
this.gamebookPublisher = gamebookPublisher;
this.gamebookStarRating = gamebookStarRating;
}
public long getGamebookId() {
return gamebookId;
}
public String getGamebookName() {
return gamebookName;
}
public String getGamebookComment() {
return gamebookComment;
}
public String getGamebookPublisher() {
return gamebookPublisher;
}
public float getGamebookStarRating(){
return gamebookStarRating;
}
public void setGamebookId(long gamebookId) {
this.gamebookId = gamebookId;
}
}
SOLVED
Finally sorted this by adding an Observer to my DAO method which returns a single gamebook. Within the Observer's onChanged() method, I can loop through all Gamebooks in the LiveData List (even though there's only one because I'm limiting it to one in the SQL query) and call getId() to get their respective IDs.
mainViewModel.getSingleGamebook().observe(this, new Observer<List<Gamebook>>() {
#Override
public void onChanged(List<Gamebook> gamebooks) {
int i=0;
for(Gamebook gamebook : gamebooks){
gamebookId= gamebook.getGamebookId();
Log.d(TAG, "Gamebook Name: "+gamebook.getGamebookName()+ " Database ID: " +gamebookId);
i++;
}
}
});
I believe that your issue is due to the only constructor being available not setting the id so the LiveData uses the default value of 0 for a long.
I'd suggest having a default constructor and thus all setters/getters and (optionally) using #Ignore annotation for one of the constructors..
without #Ignore you get warnings Gamebook.java:8: warning: There are multiple good constructors and Room will pick the no-arg constructor. You can use the #Ignore annotation to eliminate unwanted constructors. public class Gamebook {
e.g. :-
#Entity(tableName = "gamebooks_table")
public class Gamebook {
#PrimaryKey(autoGenerate = true)
private long gamebookId;
private String gamebookName;
private String gamebookComment;
private String gamebookPublisher;
private float gamebookStarRating;
public Gamebook(){} /*<<<<< ADDED */
#Ignore /*<<<<< ADDED - is not required - could be on the default constructor but not both*/
public Gamebook(String gamebookName, String gamebookComment, String gamebookPublisher, float gamebookStarRating) {
this.gamebookName = gamebookName;
this.gamebookComment = gamebookComment;
this.gamebookPublisher = gamebookPublisher;
this.gamebookStarRating = gamebookStarRating;
}
public long getGamebookId() {
return gamebookId;
}
public String getGamebookName() {
return gamebookName;
}
public String getGamebookComment() {
return gamebookComment;
}
public String getGamebookPublisher() {
return gamebookPublisher;
}
public float getGamebookStarRating(){
return gamebookStarRating;
}
public void setGamebookId(long gamebookId) {
this.gamebookId = gamebookId;
}
/* ADDED setters */
public void setGamebookName(String gamebookName) {
this.gamebookName = gamebookName;
}
public void setGamebookComment(String gamebookComment) {
this.gamebookComment = gamebookComment;
}
public void setGamebookPublisher(String gamebookPublisher) {
this.gamebookPublisher = gamebookPublisher;
}
public void setGamebookStarRating(float gamebookStarRating) {
this.gamebookStarRating = gamebookStarRating;
}
}
You also probably want to be able to pass the respective id to the getSingleGamebookByID, so you may wish to change this to:-
#Query("SELECT * FROM gamebooks_table WHERE gamebookId=:gamebookId /*<<<<< ADDED to use id passed */ ORDER BY gamebookId DESC LIMIT 1")
LiveData<Gamebook> getSingleGamebookByID(long gamebookId /*<<<<< ADDED to use id passed */);
you would probably want to remove the comments.
Note the LiveData aspect has not been tested and is conjecture.
Example
This example shows that room is fine with your original code but that the issues is on the LiveData/Viewmodel side :-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
GamebookDao dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Note The Database has .allowMainThreadQueries */
db = TheDatabase.getInstance(this);
dao = db.getGamebookDao();
long gb1id = dao.insert(new Gamebook("Gamebook1","blah","Gamebook1 Publisher", 10.1F));
long gb2id = dao.insert(new Gamebook("Gamebook2","blah","Gamebook2 Publisher", 6.1F));
long gb3id = dao.insert(new Gamebook("Gamebook3","blah","Gamebook3 Publisher", 10.1F));
logGameBook(dao.getSingleGamebookByID());
logGameBook(dao.getSingleGamebookByID());
logGameBook(dao.getSingleGamebookByID());
/* Alternative that allows the ID to be specified */
logGameBook(dao.getSingleGamebookByIDAlternative(gb1id));
logGameBook(dao.getSingleGamebookByIDAlternative(gb2id));
logGameBook(dao.getSingleGamebookByIDAlternative(gb3id));
}
void logGameBook(Gamebook gb) {
Log.d("GAMEBOOKINFO","Gamebook is " + gb.getGamebookName() + " id is " + gb.getGamebookId());
}
}
The above uses your original code, the TheDatabase is a basic #Database annotated class BUT with .allowMainThreadQueries so it is run on the main thread.
The log, after running, includes:-
2022-03-12 08:16:12.556 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
2022-03-12 08:16:12.558 I/chatty: uid=10132(a.a.so71429144javaroomidreturnedaszero) identical 1 line
2022-03-12 08:16:12.561 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
2022-03-12 08:16:12.568 D/GAMEBOOKINFO: Gamebook is Gamebook1 id is 1
2022-03-12 08:16:12.572 D/GAMEBOOKINFO: Gamebook is Gamebook2 id is 2
2022-03-12 08:16:12.574 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
Note how the first just returns the same object and thus id.
In the below code, Why sonarqube is not finding possible null pointer exception in "updateData" method?
public class PropertyObject extends LinkedHashMap<String, Object> {
/**
* Unique serialization id.
*/
private static final long serialVersionUID = 4789053897514939L;
}
public class BaseObject extends PropertyObject {
#JsonProperty("_id")
public String getId() {
return String.valueOf(this.get("_id"));
}
#JsonProperty("_id")
public void setId(Object id) {
this.put("_id", String.valueOf(id));
}
public String getName() {
return (String) this.get("name");
}
public void setName(String name) {
this.put("name", name);
}
}
private void updateData(BaseObject baseObject) {
List<Map<String, String>> link = (List<Map<String, String>>) baseObject.get("ratioMap");
for (Map<String, String> linkmap : link) {
}
}
}
I can see potential null pointer exception in updateData method in line number 2.
Is there any way by which I can make sonarqube to find these issues by itself?
First of all Sonar is a static code analysis tool. It depends on simple declarations to look for possible NPEs. Second I assume that you have an active rule for detecting possible NullPointer dereferences.
Last but not least I think that it would not detect NPEs in private methods which is not called...
I have the following entities:
public class ComplexEntity {
public List<TenderLocation> tenderList;
public ComplexEntity(List<TenderLocation> tenderList) {
this.tenderList = tenderList;
}
}
public class TenderLocation {
public String location;
public List<TenderAirline> tenderAirlines;
public TenderLocation(String location, List<TenderAirline> tenderAirlines) {
this.tenderAirlines = tenderAirlines;
this.location = location;
}
}
public class TenderAirline {
public int ID;
public String name;
public TenderAirline(int ID, String name) {
this.ID = ID;
this.name = name;
}
}
And the following test for comparing two ComplexEntiey:
public class ComplexObjectGraphComparisonExample {
#Test
public void shouldCompareTwoComplexObjects() {
// given
Javers javers = JaversBuilder.javers().build();
// Construct test data
// ComplexEntity:
// - List<TLocation>
// TLoation:
// - location: String
// - List<TAir>
// TAir:
// - int ID
// - String Name
int locations = 3;
List<TenderLocation> tenderLocationsBase = new ArrayList<TenderLocation>(locations);
List<TenderLocation> tenderLocationsRef = new ArrayList<TenderLocation>(locations);
for (int j = 0; j < locations; ++j) {
int airlines = 10;
List<TenderAirline> tenderAirlinesBase = new ArrayList<TenderAirline>(airlines);
List<TenderAirline> tenderAirlinesRef = new ArrayList<TenderAirline>(airlines);
for (int i = 0; i < airlines; ++i) {
tenderAirlinesBase.add(new TenderAirline(i, "Airline" + i));
tenderAirlinesRef.add(new TenderAirline(i, "Airline" + i));
}
tenderLocationsBase.add(new TenderLocation("BV" + j, tenderAirlinesBase));
tenderLocationsRef.add(new TenderLocation("BV" + j, tenderAirlinesBase));
}
ComplexEntity baseEntity = new ComplexEntity(tenderLocationsBase);
ComplexEntity referenceEntity = new ComplexEntity(tenderLocationsRef);
// when
Diff diff = javers.compare(baseEntity, referenceEntity);
assertThat(diff.getChanges()).hasSize(0);
// Change a single small thing
referenceEntity.tenderList.get(1).location = "Difference_1";
// then there is a single change detected
diff = javers.compare(baseEntity, referenceEntity);
assertThat(diff.getChanges()).hasSize(1);
// there should be one change of type {#link ValueChange}
ValueChange change = diff.getChangesByType(ValueChange.class).get(0);
assertThat(change.getPropertyName()).isEqualTo("location");
assertThat(change.getLeft()).isEqualTo("BV1");
assertThat(change.getRight()).isEqualTo("Difference_1");
// do another change
referenceEntity.tenderList.get(1).tenderAirlines.get(1).name = "Difference_2";
// second difference is not detected, failing the commented test
diff = javers.compare(baseEntity, referenceEntity);
assertThat(diff.getChanges()).hasSize(2);
System.out.println(diff);
}
}
At comparison my second change is not identified because the compare method is not comparing in depth my lists.
I have read here
http://www.atetric.com/atetric/javadoc/org.javers/javers-core/1.3.4/org/javers/core/Javers.html
that if I "wrap collections in some Value Objects" the deep comparing of the collection is possible.
My question is, How exactly I can wrap my collection into Value Objects?
You can wrap the object something like below:
public class Wrapper
{
private final WrappedObject obj;
public Wrapper (WrappedObject obj)
{
this.obj = obj;
}
}
What is wrong in you code is mapping, you didn't do it at all. You should map your entities as Entities using #Id annotation:
public class TenderLocation {
#Id
public String location;
...
public class TenderAirline {
#Id
public int ID;
public String name;
...
Otherwise, JaVers maps your classes as Value Objects (objects without identity) which gives you limited diff experience.
I have a property file with key/value pairs that looks like below:
#state/city/counties
fl.regionA.counties=abc,def,ghi,jkl
fl.regionB.counties=xyz,qrs,tuv,wxy
The property file is loaded via code snippet below:
#Configuration
public class Config {
#Bean
public static PropertyPlaceholderConfigurer properties(){
...
}
How can I load the a list of StateRegionMappings where StateRegionMapping:
public class StateRegionMapping{
private List<String> counties;
private String state;
private String region;
...
}
Update 5/23/2017:
My other alternate key/value structure is like this:
states.config[0].state=fl
states.config[0].name=regionA
states.config[0].counties=abc,def,ghi,jkl
states.config[1].state=fl
states.config[1].name=regionB
states.config[1].counties=xyz,qrs,tuv,wxy
If I get you right, you want to configure a list of states where each state has a region and each region a list of counties. With properties files this would look like this:
states-config.states[0].name=fl
states-config.states[0].regions[0].name=regionA
states-config.states[0].regions[0].counties[0]=abc
states-config.states[0].regions[0].counties[1]=def
states-config.states[0].regions[0].counties[2]=ghi
states-config.states[0].regions[0].counties[3]=jkl
states-config.states[0].regions[1].name=regionB
states-config.states[0].regions[1].counties[0]=xyz
states-config.states[0].regions[1].counties[1]=qrs
states-config.states[0].regions[1].counties[2]=tuv
states-config.states[0].regions[1].counties[3]=wxy
With YAML this is a bit more readable:
states-config:
states:
- name: fl
regions:
- name: regionA
counties:
- abc
- def
- ghi
- jkl
- name: regionB
counties:
- xyz
- qrs
- tuv
- wxy
The needed java config would look like this:
#ConfigurationProperties("states-config")
public class Config {
private List<State> states = new ArrayList<>();
public List<State> getStates() {
return states;
}
public void setStates(List<State> states) {
this.states = states;
}
public static class State {
private String name;
private List<Region> regions = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Region> getRegions() {
return regions;
}
public void setRegions(List<Region> regions) {
this.regions = regions;
}
}
public static class Region {
private String name;
private List<String> counties = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getCounties() {
return counties;
}
public void setCounties(List<String> counties) {
this.counties = counties;
}
}
}
To your update: this would work with the following config using your StateRegionMapping class:
#ConfigurationProperties("states")
public class Config {
private List<StateRegionMapping> config = new ArrayList<>();
public List<StateRegionMapping> getConfig() {
return config;
}
public void setConfig(List<StateRegionMapping> config) {
this.config = config;
}
}
Just one minor change: since in your StateRegionMapping class the region is named region, your must use this also in the properties instead of name:
states.config[0].state=fl
states.config[0].region=regionA
....
It is absolutely possible! Try this.
This should be present in your class where you want to load.
#Value("#{${passingscore.subjectwise}}")
private Map<String, String> passingScorePerSubjectMap;
The should be present in the application.properties file and define the key-value map properties like below.
passingscore.subjectwise= {'Maths': '40','Physics':'50','Chemistry':'70'}
Is it possible to use inheritance in Spring Boot YML configuration classes? If so, how would that be accomplished?
For example:
#ConfigurationProperties(prefix="my-config")
public class Config {
List<Vehicle> vehicles;
}
And the class (or interface) "Vehicle" has two implementations: Truck and Car. So the YAML might look like:
my.config.vehicles:
-
type: car
seats: 3
-
type: truck
axles: 3
I do not think it is possible (at least not that I know of). You could however design your code as follow:
Inject the properties into a Builder object
Define an object with all properties, which we'll call the VehicleBuilder (or factory, you choose its name).
The VehicleBuilders are injected from the Yaml.
You can then retrieve each builder's vehicle in a #PostConstruct block. The code:
#ConfigurationProperties(prefix="my-config")
#Component
public class Config {
private List<VehicleBuilder> vehicles = new ArrayList<VehicleBuilder>();
private List<Vehicle> concreteVehicles;
public List<VehicleBuilder> getVehicles() {
return vehicles;
}
public List<Vehicle> getConcreteVehicles() {
return concreteVehicles;
}
#PostConstruct
protected void postConstruct(){
concreteVehicles = vehicles.stream().map(f -> f.get())
.collect(Collectors.<Vehicle>toList());
}
}
The builder:
public class VehicleBuilder {
private String type;
private int seats;
private int axles;
public Vehicle get() {
if ("car".equals(type)) {
return new Car(seats);
} else if ("truck".equals(type)) {
return new Trunk(axles);
}
throw new AssertionError();
}
public void setType(String type) {
this.type = type;
}
public void setSeats(int seats) {
this.seats = seats;
}
public void setAxles(int axles) {
this.axles = axles;
}
}