How to chain queries with rxjava and room - java

I need to fill the fields of an object in order to post it to an API.
I am using rxjava and room but my chain of orders is failling
My daos
#Dao
abstract public class PokemonDao implements BaseDao<Pokemon>{
#Query("SELECT * FROM pokemons ORDER BY id ASC")
abstract public Flowable<List<Pokemon>> getAll();
}
#Dao
abstract public class NoteDao implements BaseDao<Note>{
#Query("SELECT * FROM notes WHERE idPokemon = :idPokemon ORDER BY registerDate DESC")
abstract public Flowable<List<Note>> getNotes(int idPokemon);
}
I need to create an object that has the data of the pokemon with a list of notes associated
I did the following on my viewmodel
pokemonRepository.getFavourites()
.toObservable()
.flatMap(new Function<List<Pokemon>, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(List<Pokemon> favourites) throws Exception {
return Observable.fromIterable(favourites);
}
})
.flatMap(new Function<Object, ObservableSource<?>>() {
#Override
public ObservableSource<?> apply(Object o) throws Exception {
return getNotesObservable((Favourite) o);
}
})
.toList()
.toObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(new SingleObserver<List<Object>>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(List<Object> objects) {
}
#Override
public void onError(Throwable e) {
}
})
I also use this method
private Observable<Object> getNotesObservable(Pokemon favourite) {
Observable<Object> lolo = noteRepository.getNotes(Integer.parseInt(favourite.getId()))
.map(new Function<List<Note>, Object>() {
#Override
public Favourite apply(List<Note> notes) throws Exception {
favourite.notesList= notes;
return favourite;
}
})
.toObservable();
return lolo;
}
My problem is that on the subscribeWith onNext method is never called.
My goal it that when onNext is called it should have a list of pokemon and each pokemon should have their notes
Thanks

Below I describe Room-ish way for your task without RxJava
Let's say you have these entities:
#Entity
public class Pokemon {
#PrimaryKey public int id;
public String name;
// other fields
........
}
#Entity
public class Note {
#PrimaryKey public int noteId;
public int pokemonId;
// other fields
........
}
Then you can add another class (it's just a class with no connection to SQLite):
public class PokemonWithNotes {
#Embedded public Pokemon pokemon; // here you'll get your pokemon
#Relation(
parentColumn = "id",
entityColumn = "pokemonId"
)
public List<Note> notesList; // here you'll het your notes' list
}
and add method to your dao:
#Transaction
#Query("SELECT * FROM Pokemon")
public List<PokemonWithNotes> getPokemonListWithNotes();
Room orders to this method to get both Pokemons and Notes and connect them (without two queries)
Using this method you'll get your List with Pokemons and notes.

Related

How to use Dagger2 in Room Database android application in java

I have a very difficult time trying to implement Dagger2 to my android app.
In my app I use RoomDB. In the beginning, I used one MainActivity class and I have successfully implemented RoomDB. It worked as aspected. I could set and get values from my database. However, later I decided to expand my app by moving my logic from MainActivity class to fragments where each fragment is identical just it writes data or gets data from a database based on a different type of product. I have tried to make this work by implementing dependency injection Dagger2. I have watched plenty of videos and read a bunch of articles on how to do this however no luck so far.
I tried to implement one example based on the article Integrate Dagger 2 with Room Persistence Library in few lines
In order not to destroy my existing application I have created a new project with the same structure as in the article. in the new project, I have used the same data structure as I have in my application. The application builds successfully. However, I can't retrieve my data from the database and I don't know why.
Here is what I did:
Product.class
#Entity(tableName = "product_table")
public class Product implements Serializable {
#PrimaryKey(autoGenerate = true)
private int ID;
#ColumnInfo(name = "product_count")
private String count;
#ColumnInfo(name = "time")
private String time;
#ColumnInfo(name = "p_type")
private String product_type;
public String getProduct_type() {
return product_type;
}
public void setProduct_type(String product_type) {
this.product_type = product_type;
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
public String getCount() {
return count;
}
public void setCount(String count) {
this.count = count;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
ProductDao
#Dao
public interface ProductDao {
#Insert(onConflict = REPLACE)
void insert(Product product);
#Delete
void delete (Product product);
#Delete
void reset(List<Product> product);
#Query("UPDATE product_table SET product_count=:sCount WHERE ID = :sID")
void update(int sID, String sCount);
#Query("SELECT * FROM product_table")
List<Product> getAll();
#Query("SELECT SUM(product_count)FROM product_table WHERE p_type = 'used' ")
String getProductTotalCount();
}
AppComponent
#Singleton
#Component(dependencies = {}, modules = {AppModule.class, RoomModule.class})
public interface AppComponent {
void inject(MainActivity mainActivity);
ProductDao productDao();
DemoDatabase2 demoDatabase2();
ProductRepository productRepository();
Application application();
}
AppModule
#Module
public class AppModule {
Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
#Provides
#Singleton
Application providesApplication() {
return mApplication;
}
}
RoomModule
#Module
public class RoomModule {
private DemoDatabase2 demoDatabase2;
public RoomModule(Application mApplication) {
demoDatabase2 = Room.databaseBuilder(mApplication, DemoDatabase2.class, "demo-db").build();
}
#Singleton
#Provides
DemoDatabase2 providesRoomDatabase() {
return demoDatabase2;
}
#Singleton
#Provides
ProductDao providesProductDao(DemoDatabase2 demoDatabase2) {
return demoDatabase2.getProductDao();
}
#Singleton
#Provides
ProductRepository productRepository(ProductDao productDao) {
return new ProductDataSource(productDao);
}
}
DemoDatabase2
#Database(entities = {Product.class}, version = DemoDatabase2.VERSION, exportSchema = false)
public abstract class DemoDatabase2 extends RoomDatabase {
static final int VERSION = 2;
public abstract ProductDao getProductDao();
}
ProductDataSource
public class ProductDataSource implements ProductRepository {
private ProductDao productDao;
#Inject
public ProductDataSource(ProductDao productDao) {
this.productDao = productDao;
}
#Override
public void insert(Product product) {
}
#Override
public void delete(Product product) {
}
public String provideProductCount() {
return productDao.getProductTotalCount();
}
#Override
public List<Product> getAll() {
return productDao.getAll();
}
}
ProductRepository
public interface ProductRepository {
void insert(Product product);
void delete(Product product);
List<Product> getAll();
String provideProductCount();
}
MainActivity
public class MainActivity extends AppCompatActivity {
#Inject
public ProductRepository productRepository;
TextView mTextView;
EditText mEditText, mEditTextType;
Button mButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerAppComponent.builder()
.appModule(new AppModule(getApplication()))
.roomModule(new RoomModule(getApplication()))
.build()
.inject(this);
mTextView = findViewById(R.id.text);
mEditTextType = findViewById(R.id.product_type);
mEditText = findViewById(R.id.productCount);
mButton = findViewById(R.id.button);
}
public void addValue(View view) {
Product product = new Product();
String count = mEditText.getText().toString();
product.setCount(count);
String type = mEditTextType.getText().toString();
product.setProduct_type(type);
mTextView.setText(String.valueOf(product.getCount()));
}
}
How can I get or set data as provideProductCount() from my DB? As if I try to use something like
String producCount = productRepository.provideProductCount();
It gives error as:
Caused by: java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
at androidx.room.RoomOpenHelper.onUpgrade(RoomOpenHelper.java:117)
How to fix this?
You have to use fallbackToDestructiveMigration() to allow Room recreate database when you change db
public RoomModule(Application mApplication) {
demoDatabase2 = Room.databaseBuilder(mApplication, DemoDatabase2.class, "demo-db")
.fallbackToDestructiveMigration()
.build();
}
Visit here for more detail
First of all, As I can see your error doesn't relate to the Dagger. For migrating the database check this link:
https://developer.android.com/training/data-storage/room/migrating-db-versions
And the way you provided the context for creating the database is wrong.
I built a demo project with Dagger, RoomDB, but It was written by Kotlin.
https://github.com/frank-nhatvm/expensestracker
I hope that can help you. Check in the di/AppComponent.kt to know to provide a context using Dagger.

An issue with recieving List of Objects from Room database through Dao, Repository and Viewmodel. Cannot find mistake

I have methods in Dao, in Repository, in ViewModel and in Activity to get list of Persons,
but I get empty list in the end (Null object reference error).
WHere can be a mistake? THanks a lott..
In PersonDao:
#Dao
public interface PersonDao {
//other methods of PersonDao
#Query("SELECT * FROM person_table WHERE status = :status ORDER BY RANDOM() LIMIT 5")
List<Person> getFivePersonsFrom(String status);
}
In PersonRepository:
public class PersonRepository {
private PersonDao mPersonDao;
private List<Person> mFivePersonsFrom;
//other methods
List<Person> getFivePersonsFrom(String status) {
PersonRoomDatabase.databaseWriteExecutor.execute(() -> {
mPersonDao.getFivePersonsFrom("noob");
});
return mFivePersonsFrom;
}
}
In PersonViewModel:
public class PersonViewModel extends AndroidViewModel {
private PersonRepository mRepository;
public List<Person> mFivePersonsFrom;
public PersonViewModel(#NonNull Application application) {
super(application);
mRepository = new PersonRepository(application);
mFivePersonsFrom = mRepository.getFivePersonsFrom("noob");
//other methods
}
public List<Person> getFivePersonsFrom() {
mRepository.getFivePersonsFrom("noob");
return mFivePersonsFrom;
}
}
In MainActivity:
private CardStackView noobCardStackView;
private NoobAdapter noobAdapter;
List<Person> noobList;
// other
protected void onCreate(Bundle savedInstanceState) {
noobViewModel = new ViewModelProvider(this,
ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()))
.get(PersonViewModel.class);
noobList = noobiewModel.getFivePersonsFrom();
noobAdapter = new NoobAdapter(new NoobAdapter.NoobDiff(), noobList);
noobCardStackView.setAdapter(noobAdapter);
// methods
}

Generic class for CRUD operations for domain models

My goal is to use a generic class for CRUD operations so that I dont need to implement a seperate class for each domain model in my app.
This layer also converts between my DTOs and the domain model.
The get and delete methods work fine. However, how can I implement the save method. In case of a new entity, I need to create a new instance of the generic and map the DTO on it.
/**
* This class can be extended to use default CRUD features
*
* #param <T> domain model
* #param <V> DTO represenation of domain model
*/
#RequiredArgsConstructor
public abstract class AbstractCrudService<T extends CoreDomain, V extends CoreDTO> implements CrudStrategy<T, V>{
private final JpaRepository<T, Long> repository;
private final ModelMapper modelMapper;
private final Class<V> dtoClass;
#Override
public List<V> getAll() {
List<V> list = new ArrayList<>();
for (T item : repository.findAll()) {
list.add(modelMapper.map(item, dtoClass));
};
return list;
}
#Override
public V getById(Long id) {
T entity = getEntity(id);
return modelMapper.map(entity, dtoClass);
}
#Override
public void delete(Long id) {
repository.deleteById(id);
}
#Override
#Transactional
public void save(V dto) {
T entity = new T(); /// DOESNT WORK!!!!
// for edit operation, load existing entity from the DB
if (dto.getId() != null) {
entity = getEntity(dto.getId());
}
modelMapper.map(dto, entity);
}
#Override
public T getEntity(Long id) {
Optional<T> entity = Optional.ofNullable(repository.findById(id)
.orElseThrow(() -> new NoSuchElementException(String.format("Entity with id %s not found", id))));
return entity.get();
}
}
My Service class looks like this:
#Service
public class Test extends AbstractCrudService<Project, ProjectDTO>{
private final ProjectRepository projectRepository;
private final ModelMapper modelMapper;
public Test(ProjectRepository projectRepository, ModelMapper modelMapper) {
super(projectRepository, modelMapper, ProjectDTO.class);
this.projectRepository = projectRepository;
this.modelMapper = modelMapper;
}
}
I solved it with following implementation:
#Override
#Transactional
#SuppressWarnings("unchecked")
public void save(V dto) {
ParameterizedType paramType = (ParameterizedType) entityClass.getGenericSuperclass();
T entity = (T) paramType.getActualTypeArguments()[0];
// for edit operation, load existing entity from the DB
if (dto.getId() != null) {
entity = getEntity(dto.getId());
}
modelMapper.map(dto, entity);
}

Android architecture components : Room : No such table

I'm trying to use the new Architecture components, but when I try to run, I get :
"Error:(375, 24) error: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such table: posts)"
The following are my classes.
**ENTITY : **
#Entity
public static class Post {
#PrimaryKey
private String id;
#ColumnInfo(name = "data")
private String data;
public String getId() {
return id;
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setId(String id) {
this.id = id;
}
}
DAO :
#Dao
public interface PostDao {
#Query("SELECT * FROM posts")
LiveData<List<Post>> getAll();
#Insert
void insertAll(Post... posts);
#Insert
void insert(Post post);
#Delete
void delete(Post post);
}
The database :
#Database(entities = {Post.class}, version = 1)
public static abstract class AppDatabase extends RoomDatabase {
public abstract PostDao postDao();
}
By default, Room uses the class name as the database table name. If you want the table to have a different name, set the tableName property of the #Entity annotation, as shown in the following code snippet:
https://developer.android.com/topic/libraries/architecture/room.html
It seems you assumed it would pluralize the class on its own.
So, either use SELECT * FROM Post
or do
#Entity(tableName = "posts")
class Post {
...
}

Which pattern to use to avoid code duplication with object value transformer

I want to get rid of the following code duplication within the MyFacadeBean. Consider the following situation:
public class FacadeBean implements Facade {
#EJB
private CrudService crudService;
#Inject
private FirstAssembler firstAssembler;
#Inject
private SecondAssembler secondAssembler;
#Inject
private ThirdAssembler thridAssembler;
#Inject
private FourthAssembler fourthAssembler;
#Override
public void save(FirstValue value) {
FirstEntity entity = this.firstAssembler.transformToEntity(value);
this.crudService.persist(entity);
}
#Override
public void save(SecondValue value) {
SecondEntity entity = this.secondAssembler.transformToEntity(value);
this.crudService.persist(entity);
}
#Override
public void save(ThirdValue value) {
ThirdEntity entity = this.thirdAssembler.transformToEntity(value);
this.crudService.persist(entity);
}
#Override
public void save(FourthValue value) {
FourthEntity entity = this.fourthAssembler.transformToEntity(value);
this.crudService.persist(entity);
}
}
public interface MyFacade {
void save(FirstValue value);
void save(SecondValue value);
}
With the CrudService:
public interface CrudService {
void persist(Object entity);
}
#Stateless
#Local(CrudService.class)
#TransactionAttribute(TransactionAttributeType.MANDATORY)
public class CrudServiceBean implements CrudService {
public static final String PERSISTENCE_UNIT_NAME = "my_persistence_unit";
private EntityManager entityManager;
#PersistenceContext(unitName = PERSISTENCE_UNIT_NAME)
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
#Override
public void persist(Object entity) {
this.entityManager.persist(entity);
}
}
With the following assemblers:
public class FirstAssembler extends AbstractAssembler<FirstEntity> {
public FirstEntity transformToEntity(FirstValue value) {
if (value == null)
return null;
FirstEntity entity = new FirstEntity();
transformAbstractValueToAbstractObject(value, entity);
entity.setFixedRate(value.getFixedRate());
entity.setStartDate(value.getStartDate());
return entity;
}
}
public class SecondAssembler extends AbstractAssembler<SecondEntity> {
public SecondEntity transformToEntity(SecondValue value) {
if (value == null)
return null;
SecondEntity entity = new SecondEntity();
transformAbstractValueToAbstractObject(value, entity);
entity.setTransactionType(value.getTransactionType());
entity.setValueDate(value.getValueDate());
return entity;
}
}
public abstract class AbstractAssembler<T extends AbstractEntity> {
protected void transformAbstractValueToAbstractObject(AbstractValue value, T object) {
object.setUniqueId(value.getUniqueId());
object.setNominalAmountValue(value.getNominalAmountValue());
}
}
With the following entities:
#Entity
public class FirstEntity extends AbstractEntity {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private Long id;
#Column(name = "START_DATE")
#Temporal(TemporalType.DATE)
private Date startDate;
#Column(name = "FIXED_RATE")
#Digits(integer = 1, fraction = 10)
private BigDecimal fixedRate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public BigDecimal getFixedRate() {
return fixedRate;
}
public void setFixedRate(BigDecimal fixedRate) {
this.fixedRate = fixedRate;
}
}
#Entity
public class SecondEntity extends AbstractEntity {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private Long id;
#Column(name = "VALUE_DATE")
#Temporal(TemporalType.DATE)
private Date valueDate;
#Column(name = "TRANSACTION_TYPE")
#Enumerated(EnumType.STRING)
private TransactionType transactionType;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getValueDate() {
return valueDate;
}
public void setValueDate(Date valueDate) {
this.valueDate = valueDate;
}
public TransactionType getTransactionType() {
return transactionType;
}
public void setTransactionType(TransactionType transactionType) {
this.transactionType = transactionType;
}
}
#MappedSuperclass
public abstract class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "TRANSACTION_NOM_AMOUNT_VALUE")
#Digits(integer = 18, fraction = 5)
#Min(0)
private BigDecimal nominalAmountValue;
public BigDecimal getNominalAmountValue() {
return nominalAmountValue;
}
public void setNominalAmountValue(BigDecimal nominalAmountValue) {
this.nominalAmountValue = nominalAmountValue;
}
}
I tried the following approach:
public class FacadeBean implements Facade {
#Inject
private Assembler assembler;
#Inject
private AssemblerFactory assemblerFactory;
#Override
public <T extends AbstractValue> void save(T value) {
Assembler assembler = assemblerFactory.createAssembler(value);
AbstractEntity entity = assembler.transformToEntity(value);
this.crudService.persist(entity);
}
}
Problems are the AssemblerFactoryImpl and the AssemblerImpl in which I have to do instanceOf checks and castings...
Another idea would be to let the value know which transformer to use (or how to transform). But I want the value to be "dumb".
#Glenn Lane
public AbstractValue save(AbstractValue value) {
AbstractAssembler<AbstractValue, AbstractEntity> assembler = new FirstAssembler();
AbstractEntity entity = assembler.transformToEntity(value);
AbstractValue result = assembler.transformToValue(entity);
return result;
}
does not work, because of
Type mismatch: cannot convert from FirstAssembler to AbstractAssembler
I'm posting this as a separate answer, since I don't really think there's anything wrong with having a save method for every AbstractValue type.
First we'll establish your base value class for this example. I'm using an interface just so we don't muddy the waters. Your AbstractValue interface:
public interface AbstractValue
{
int getUniqueId();
double getNominalValue();
<T> T accept(AbstractValueVisitor<T> visitor);
}
And the "visitor interface":
public interface AbstractValueVisitor<T>
{
T visit(FirstValue value);
T visit(SecondValue value);
T visit(ThirdValue value);
T visit(FourthValue value);
}
I know you don't want intelligence baked into AbstractValue, but we are going to add one specification... that all concrete implementations of AbstractValue (all four) implement the accept method exactly this way:
#Override
public <T> T accept(AbstractValueVisitor<T> visitor)
{
return visitor.visit(this);
}
So that method is implemented four times: in all four value classes, exactly the same way. Because the visitor interface is aware of all concrete implementations, the appropriate method will be called for each particular value type. All three of these parts put together is the "visitor pattern".
Now we'll make an entity factory. Its job is to create the appropriate AbstractEntity when provided an AbstractValue:
public class AbstractEntityFactory
implements AbstractValueVisitor<AbstractEntity>
{
private static final AbstractEntityFactory INSTANCE;
static
{
INSTANCE = new AbstractEntityFactory();
}
// Singleton pattern
private AbstractEntityFactory()
{
}
public static AbstractEntity create(AbstractValue value)
{
if (value == null)
{
return null;
}
AbstractEntity e = value.accept(INSTANCE);
e.setNominalValue(value.getNominalValue());
e.setUniqueId(value.getUniqueId());
return e;
}
#Override
public AbstractEntity visit(FirstValue value)
{
FirstEntity entity = new FirstEntity();
// Set all properties specific to FirstEntity
entity.setFixedRate(value.getFixedRate());
entity.setStartDate(value.getStartDate());
return entity;
}
#Override
public AbstractEntity visit(SecondValue value)
{
SecondEntity entity = new SecondEntity();
// Set all properties specific to SecondEntity
entity.setTransactionType(value.getTransactionType());
entity.setValueDate(value.getValueDate());
return entity;
}
#Override
public AbstractEntity visit(ThirdValue value)
{
ThirdEntity entity = new ThirdEntity();
// Set all properties specific to ThirdEntity
return entity;
}
#Override
public AbstractEntity visit(FourthValue value)
{
FourthEntity entity = new FourthEntity();
// Set all properties specific to FourthEntity
return entity;
}
}
Now your facade implementation takes an AbstractValue, and you got that one save method you're looking for:
public class FacadeBean implements Facade
{
#EJB
private CrudService crudService;
#Override
public void save(AbstractValue value)
{
AbstractEntity entity = AbstractEntityFactory.create(value);
crudService.persist(entity);
}
}
Because your AbstractValue now follows the visitor pattern, you can do all sorts of polymorphic behavior. Such as:
public class AbstractValuePrinter implements AbstractValueVisitor<Void>
{
private final Appendable out;
public AbstractValuePrinter(Appendable out)
{
this.out = out;
}
private void print(String s)
{
try
{
out.append(s);
out.append('\n');
}
catch (IOException e)
{
throw new IllegalStateException(e);
}
}
#Override
public Void visit(FirstValue value)
{
print("I'm a FirstValue!");
print("Being a FirstValue is groovy!");
return null;
}
#Override
public Void visit(SecondValue value)
{
print("I'm a SecondValue!");
print("Being a SecondValue is awesome!");
return null;
}
#Override
public Void visit(ThirdValue value)
{
print("I'm a ThirdValue!");
print("Meh.");
return null;
}
#Override
public Void visit(FourthValue value)
{
print("I'm a ThirdValue!");
print("Derp.");
return null;
}
}
In this example, this visitor isn't returning anything... it's "doing" something, so we'll just set the return value as Void, since it's non-instantiatable. Then you print the value simply:
// (value must not be null)
value.accept(new AbstractValuePrinter(System.out));
Finally, the coolest part of the visitor pattern (in my opinion): you add FifthValue. You add the new method to your visitor interface:
T visit(FifthValue value);
And suddenly, you can't compile. You must address the lack of this handling in two places: AbstractEntityFactory and AbstractValuePrinter. Which is great, because you should consider it in those places. Doing class comparisons (with either instanceof or rinde's solution of a class-to-factory map) is likely to "miss" the new value type, and now you have runtime bugs... especially if you're doing 100 different things with these value types.
Anyhoo, I didn't want to get into this, but there you go :)
Use a generic method with a bound type parameter in order to spare yourself the repetition:
public <T extends AbstractValue> T save(T value) {...}
Within the method body, you'll be able to reference the argument value with all methods pertaining to AbstractValue.
Notes
Since your save methods seem to be overrides in this example, you might need to change the design of the parent class or interface too.
You could also use a generic class to start with (instead of a generic method in a non-necessarily generic class), depending on your use case.
I think a problem in your code is that the generic type of AbstractAssembler is that of the output of the transform method, not the input. If you change it as follows:
public abstract class AbstractAssembler<T extends AbstractValue> {
protected void transformAbstractValueToAbstractObject(AbstractEntity entity, T value) {
entity.setUniqueId(value.getUniqueId());
entity.setNominalAmountValue(value.getNominalAmountValue());
}
public abstract AbstractEntity transformToEntity(T value);
}
Then you can change the FacadeBean to the following.
public class FacadeBean {
#EJB
private CrudService crudService;
final Map<Class<?>, AbstractAssembler<?>> knownAssemblers;
FacadeBean() {
knownAssemblers = new LinkedHashMap<>();
knownAssemblers.put(FirstValue.class, new FirstAssembler());
knownAssemblers.put(SecondValue.class, new SecondAssembler());
// add more assemblers here
}
public <T extends AbstractValue> void save(T value, Class<T> type) {
#SuppressWarnings("unchecked") // safe cast
final AbstractAssembler<T> assembler =
(AbstractAssembler<T>) knownAssemblers.get(type);
final AbstractEntity entity = assembler.transformToEntity(value);
this.crudService.persist(entity);
}
}
Notice that I changed the signature of the save(..) method such that we have the type of the object that needs to be saved. With this type we can simply lookup the right assembler that should be used. And because the assembler is now generic on its input type, we can do a safe cast (be careful to keep your map consistent).
This implementation avoids duplication of code as you only need one save method. The use of the instanceof operator is prevented by changing the generic type of AbstractAssembler and storing all assemblers in a map.
The assemblers can look like this:
public class FirstAssembler extends AbstractAssembler<FirstValue> {
#Override
public FirstEntity transformToEntity(FirstValue value) {
final FirstEntity entity = new FirstEntity();
// do transformational stuff
super.transformAbstractValueToAbstractObject(entity, value);
entity.setFixedRate(value.getFixedRate());
entity.setStartDate(value.getStartDate());
return entity;
}
}
public class SecondAssembler extends AbstractAssembler<SecondValue> {
#Override
public SecondEntity transformToEntity(SecondValue value) {
final SecondEntity entity = new SecondEntity();
// do transformational stuff
super.transformAbstractValueToAbstractObject(entity, value);
return entity;
}
}
Note: I'm not familiar with Java beans so you probably have to adapt the code a little if you want to use the #Injected assemblers instead of calling the constructors directly.
You're getting close to gold-plating here, but there is a bit of reduction you can do, specifically the null-check and calling the common field-setting method from each extension.
public abstract class AbstractAssembler<V extends AbstractValue, E extends AbstractEntity>
{
public final E transformToEntity(V value)
{
if (value == null)
{
return null;
}
E entity = createEntity(value);
entity.setUniqueId(value.getUniqueId());
entity.setNominalAmountValue(value.getNominalAmountValue());
return entity;
}
/**
* #return
* Appropriate entity object, with the fields not common to all AbstractEntity
* already set
*/
protected abstract E createEntity(V value);
}
And then the extended assembler:
public class FirstAssembler extends AbstractAssembler<FirstValue, FirstEntity>
{
#Override
protected FirstEntity createEntity(FirstValue value)
{
FirstEntity entity = new FirstEntity();
entity.setFixedRate(value.getFixedRate());
entity.setStartDate(value.getStartDate());
return entity;
}
}
If you really want a single factory class to handle all your values/entities, I would look into the visitor pattern, enhanced with a generic type parameter on the visitor interface (and the entity/value accept methods return a type based on the visiting interface). I won't show an example here simply because I don't think it's warranted in your case.
You can have one save method from the point of view of the classes that save those values, but you still have to implement three individual save methods.
Implement a class with all three save methods. For example:
public class ValuePersister {
#Inject
private Assembler1 assembler1;
#Inject
private Assembler2 assembler2;
#Inject
private Assembler3 assembler3;
public Value1 save(Value1 value1, CrudService crudService) {
Entity1 entity1 = assembler1.transformToObject(value1);
crudService.persist(entity1);
return assembler1.transformToValue(entity1);
}
public Value2 save(Value2 value2, CrudService crudService) {
Entity2 entity2 = assembler2.transformToObject(value2);
crudService.persist(entity2);
return assembler2.transformToValue(entity2);
}
public Value3 save(Value3 value3, CrudService crudService) {
Entity3 entity3 = assembler3.transformToObject(value3);
crudService.persist(entity3);
return assembler3.transformToValue(entity3);
}
}
Add an abstract method to AbstractValue:
public abstract AbstractValue save(ValuePersister valuePersister, CrudService crudService);
Implement that method in each class that extends AbstractValue:
#Override
public AbstractValue save(ValuePersister valuePersister, CrudService crudService) {
return valuePersister.save(this, crudService);
}
Inject ValuePersister and implement your original generic save method:
#Inject
private ValuePersister valuePersister;
#Override
public AbstractValue save(AbstractValue value) {
return value.save(valuePersister, crudService)
}

Categories