How to get a Room Database ViewModel instance inside WorkManager? - android-room

I am newbie in Android development. I am failing to get the room db viewmodel which extends AndroidViewModel inside WorkManager. I want to get a viewmodel class instance to do insert inside room db.
AppAdViewModel.class
public class AppAdViewModel extends AndroidViewModel {
private AppAdRepository appAdRepository;
public AppAdViewModel(#NonNull Application application) {
super(application);
appAdRepository = new AppAdRepository(application);
}
public void insert(AppAdModel appAdModel){
appAdRepository.insertAd(appAdModel);
}
}
MyBackgroundWorker.class
public class AppDataSyncingworker extends Worker{
private Context context;
private AppAdViewModel appAdViewModel;
public AppDataSyncingworker(#NonNull Context context, #NonNull WorkerParameters workerParams){
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
this.context = getApplicationContext();
// error on this line
this.appAdViewModel = new ViewModelProvider((AppCompatActivity)context).get(AppAdViewModel.class);
return null;
}
The error it's showing is:
java.util.concurrent.ExecutionException: java.lang.ClassCastException: android.app.Application cannot be cast to androidx.appcompat.app.AppCompatActivity
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:298)
at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.ClassCastException: android.app.Application cannot be cast to androidx.appcompat.app.AppCompatActivity
Any help or guide what i am doing wrong?

Calling getApplicationContext() should not be the preferred way of getting the Context. public constructor of Worker takes a Context as the first parameter. You can use that

Related

Vaadin Spring Boot #autowired field returns null in a view

First off, please excuse my question due to my being new to spring boot ecosystem. In my application, I've a vaadin page, where I want to submit user details to DB, using repository. In my view class, I've added them as #autowired fields, however, during the runtime, I see that their values are run so the operation fails. I know that to benefit from #autowired, the instances should not be created newly during constructing but I couldn't figure out how I should do it on my own. Here are my classes:
#SuppressWarnings("serial")
public class LoginAwareComposite extends Composite<Div> {
#Autowired
private ApplicationEventPublisher publisher;
public LoginAwareComposite() {
}
#Override
protected void onAttach(AttachEvent event) {
super.onAttach(event);
UserCredentials userPrincipal = UI.getCurrent().getSession().getAttribute(UserCredentials.class);
if (userPrincipal != null) {
// --- NOT LOGGED IN
UI.getCurrent().navigate(AddressBookManagementView.class);
}
}
}
#SuppressWarnings("serial")
#Route(value = "account")
#Theme(value = Lumo.class, variant = Lumo.LIGHT)
public class AddressBookManagementView extends LoginAwareComposite {
private VerticalLayout pageLayout = new VerticalLayout();
public AddressBookManagementView() {
getContent().setSizeFull();
getContent().add(initPage());
}
private Component initPage() {
pageLayout.getStyle().set("padding-left", "0px");
pageLayout.getStyle().set("padding-bottom", "0px");
pageLayout.getStyle().set("padding-right", "0px");
pageLayout.getStyle().set("overflow", "auto");
pageLayout.setSizeFull();
pageLayout.add(new HeaderLayout(), new BodyLayout(), new FooterLayout());
return pageLayout;
}
}
#SuppressWarnings("serial")
#SpringComponent
public class BodyLayout extends VerticalLayout {
// some fields
#Autowired
EmailRepository emailRepository;
#Autowired
FaxRepository faxRepository;
public BodyLayout() {
init(); //this function inits the view, and eventually inits the on click event for submit button , which then calls my function
}
private void myFunction() {
//here i use the repository entities but they do return null although they are autowired
}
So what happens is, in BodyLayout's constructor we call init() function which is used to init the layout and give functionality buttons etc, one of subfunctions inside the init method gives functionality to submit button using myFunction. MyFuction uses the repository entity but it returns null.
Since you are using springboot with vaadin ensure the following :
Make sure that the #Repository annotation is used on your repository interfaces like on EmailRepository.
Try using constructor injection for your repository classes like :
Try like below :
#SuppressWarnings("serial")
#SpringComponent
#UIScope
public class BodyLayout extends VerticalLayout {
// some fields
private final EmailRepository emailRepository;
private final FaxRepository faxRepository;
#Autowired
public BodyLayout(EmailRepository emailRepository, FaxRepository faxRepository) {
this.emailRepository = emailRepository;
this.faxRepository = faxRepository;
init(); //this function inits the view, and eventually inits the on click event for submit button , which then calls my function
}
private void myFunction() {
//here i use the repository entities but they do return null although they are autowired
}
I was able to get it working as follows:
#SuppressWarnings("serial")
#Route(value = "account")
#Theme(value = Lumo.class, variant = Lumo.LIGHT)
#UIScope
#SpringComponent
public class AddressBookManagementView extends LoginAwareComposite {
private VerticalLayout pageLayout = new VerticalLayout();
#Autowired
BodyLayout bodyLayout;
public AddressBookManagementView(BodyLayout bodyLayout) {
this.bodyLayout = bodyLayout;
getContent().setSizeFull();
getContent().add(initPage());
}
private Component initPage() {
pageLayout.getStyle().set("padding-left", "0px");
pageLayout.getStyle().set("padding-bottom", "0px");
pageLayout.getStyle().set("padding-right", "0px");
pageLayout.getStyle().set("overflow", "auto");
pageLayout.setSizeFull();
pageLayout.add(new HeaderLayout(), bodyLayout, new FooterLayout());
return pageLayout;
}
Then BodyLayout is
#SuppressWarnings("serial")
#UIScope
#SpringComponent
public class BodyLayout extends VerticalLayout {
private final EmailRepository emailRepository;
private final FaxRepository faxRepository;
#Autowired
public BodyLayout(EmailRepository emailRepository, FaxRepository faxRepository) {
this.emailRepository = emailRepository;
this.faxRepository = faxRepository;
init();
}
Roughly only #Route, layouts, and the vaadin init listener takes part in automatic dependency injection (that is: the vaadin spring integration asks the spring context to build them). If you do new MyClass() it never takes part in DI. Using field based injection with #Autowired hides this problem - so using constructor based injection is the "industry standard". The other way around is to not build your own instances, if you want to take part in DI but ask the spring context to build an instance for you.

JSON-B serializes Map keys using toString and not with registered Adapter

I have a JAX-RS service that returns a Map<Artifact, String> and I have registered a
public class ArtifactAdapter implements JsonbAdapter<Artifact, String>
which a see hit when deserializing the in-parameter but not when serializing the return value, instead the Artifact toString() is used. If I change the return type to a Artifact, the adapter is called. I was under the impression that the Map would be serialized with built-in ways and then the adapter would be called for the Artifact.
What would be the workaround? Register an Adapter for the whole Map?
I dumped the thread stack in my toString and it confirms my suspicions
at java.lang.Thread.dumpStack(Thread.java:1336)
Artifact.toString(Artifact.java:154)
at java.lang.String.valueOf(String.java:2994)
at org.eclipse.yasson.internal.serializer.MapSerializer.serializeInternal(MapSerializer.java:41)
at org.eclipse.yasson.internal.serializer.MapSerializer.serializeInternal(MapSerializer.java:30)
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:63)
at org.eclipse.yasson.internal.Marshaller.serializeRoot(Marshaller.java:118)
at org.eclipse.yasson.internal.Marshaller.marshall(Marshaller.java:74)
at org.eclipse.yasson.internal.JsonBinding.toJson(JsonBinding.java:98)
is the serializer hell-bent on using toString at this point?
I tried
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class PersonAdapter implements JsonbAdapter{
#Override
public String adaptToJson(Person obj) throws Exception {
return obj.getName();
}
#Override
public Person adaptFromJson(String obj) throws Exception {
return new Person(obj);
}
}
public class Test {
public static void main(String[] args) {
Map<Person, Integer> data = new HashMap<>();
data.put(new Person("John"), 23);
JsonbConfig config = new JsonbConfig().withAdapters(new PersonAdapter());
Jsonb jsonb = JsonbBuilder.create(config);
System.out.println(jsonb.toJson(data, new HashMap<Person, Integer>() {
}.getClass().getGenericSuperclass()));
}
}
but still ended up with the toString() of Person
Thanks in advance,
Nik
https://github.com/eclipse-ee4j/yasson/issues/110 (in my case since that's the default provider for WildFly)

Spring Data REST Custom Resource URI works for String but not Long

I have a model:
public class MyModel {
#Id private Long id;
private Long externalId;
// Getters, setters
}
I'd like to use externalId as my resource identifier:
#Configuration
static class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration configuration) {
configuration
.withEntityLookup()
.forRepository(MyRepository.class, MyModel::getExternalId, MyRepository::findByExternalId);
}
}
If externalId is a String, this works fine. But since it's a number (Long)
public interface MyRepository extends JpaRepository<MyModel, Long> {
Optional<MyModel> findByExternalId(#Param("externalId") Long externalId);
}
when invoking: /myModels/1 I get:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
at org.springframework.data.rest.core.config.EntityLookupConfiguration$RepositoriesEntityLookup.lookupEntity(EntityLookupConfiguration.java:213) ~[spring-data-rest-core-2.6.4.RELEASE.jar:na]
at org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFactory$UnwrappingRepositoryInvoker.invokeFindOne(UnwrappingRepositoryInvokerFactory.java:130) ~[spring-data-rest-core-2.6.4.RELEASE.jar:na]
at org.springframework.data.rest.webmvc.RepositoryEntityController.getItemResource(RepositoryEntityController.java:524) ~[spring-data-rest-webmvc-2.6.4.RELEASE.jar:na]
at org.springframework.data.rest.webmvc.RepositoryEntityController.getItemResource(RepositoryEntityController.java:335) ~[spring-data-rest-webmvc-2.6.4.RELEASE.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111]
...
A separate custom EntityLookupSupport<MyModel> component class works.
Am I missing something to get it working for Long using method references in my RepositoryRestConfigurerAdapter?
Try to add this to your RepositoryEntityLookupConfig class:
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(String.class, Long.class, Long::parseLong);
super.configureConversionService(conversionService);
}
Do you really need to set configuration by yourself ? You could try to use spring-boot auto-configuration by adding #RepositoryRestResource annotation
#RepositoryRestResource(collectionResourceRel = "myModels", path = "myModels")
public interface MyRepository extends JpaRepository<MyModel, Long> {
Optional<MyModel> findByExternalId(#Param("externalId") Long externalId);
}
Also add #Entity on your model class
#Entity
public class MyModel {
#Id
private Long id;
#Column(name = "EXTERNAL_ID")
// Column annotation is not required if you respect case-sensitive
private Long externalId;
// Getters, setters
}
Apparently, the default BackendIdConverter (see DefaultIdConverter) does nothing with ID conversion and on the other hand Spring Data Rest cannot use the repository ID type. So, you have to either convert it yourself or configure your custom ID converter bean, for example:
#Bean
public BackendIdConverter myModelBackendIdConverter() {
return new BackendIdConverter() {
#Override
public Serializable fromRequestId(final String id, final Class<?> entityType) {
return Optional.ofNullable(id).map(Long::parseLong).orElse(null);
}
#Override
public boolean supports(final Class<?> delimiter) {
return MyModel.class.isAssignableFrom(delimiter);
}
#Override
public String toRequestId(final Serializable id, final Class<?> entityType) {
return Optional.ofNullable(id).map(Object::toString).orElse(null);
}
};
}
See also:
BackendIdHandlerMethodArgumentResolver
#BackendId
The signature of the method you are trying to call seems to be:
forRepository(Class<R> type, Converter<T,ID> identifierMapping,
EntityLookupRegistrar.LookupRegistrar.Lookup<R,ID> lookup)
I don't see how MyModel::getExternalId can be doing the necessary conversion.
I would try something like the following:
#Configuration
static class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration configuration) {
configuration
.withEntityLookup()
.forRepository(MyRepository.class, Long::parseLong, MyRepository::findByExternalId);
}
}

DeferredResult with time consuming processing resulting in exception

I have one class that extends DeferredResults and extends Runnable as shown below
public class EventDeferredObject<T> extends DeferredResult<Boolean> implements Runnable {
private Long customerId;
private String email;
#Override
public void run() {
RestTemplate restTemplate=new RestTemplate();
EmailMessageDTO emailMessageDTO=new EmailMessageDTO("dineshshe#gmail.com", "Hi There");
//Very long running call
Boolean result=restTemplate.postForObject("http://localhost:9080/asycn/sendEmail", emailMessageDTO, Boolean.class);
this.setResult(result);
}
//Constructor and getter and setters
}
Now I have controller that return the object of the above class,whenever new request comes to controller we check if that request is present in HashMap(That stores unprocessed request at that instance).If not present then we are creating object of EventDeferredObject class can store that in HashMap and call start() method on it.If this type request is already present then we will return that from HashMap.On completion on request we will delete that request from HashMap.
#RequestMapping(value="/sendVerificationDetails")
public class SendVerificationDetailsController {
private ConcurrentMap<String , EventDeferredObject<Boolean>> requestMap=new ConcurrentHashMap<String , EventDeferredObject<Boolean>>();
#RequestMapping(value="/sendEmail",method=RequestMethod.POST)
public EventDeferredObject<Boolean> sendEmail(#RequestBody EmailDTO emailDTO)
{
EventDeferredObject<Boolean> eventDeferredObject = null;
System.out.println("Size:"+requestMap.size());
if(!requestMap.containsKey(emailDTO.getEmail()))
{
eventDeferredObject=new EventDeferredObject<Boolean>(emailDTO.getCustomerId(), emailDTO.getEmail());
requestMap.put(emailDTO.getEmail(), eventDeferredObject);
Thread t1=new Thread(eventDeferredObject);
t1.start();
}
else
{
eventDeferredObject=requestMap.get(emailDTO.getEmail());
}
eventDeferredObject.onCompletion(new Runnable() {
#Override
public void run() {
if(requestMap.containsKey(emailDTO.getEmail()))
{
requestMap.remove(emailDTO.getEmail());
}
}
});
return eventDeferredObject;
}
}
Now as the processing of call in threads run() method takes 50000ms time so this results in exception as:
java.lang.IllegalStateException: Cannot forward after response has been committed
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:328)
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:433)
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:299)
at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:393)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:434)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:310)
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1682)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:649)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1556)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1513)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
How to tackle this situation..if the processing time is around 20000ms code works fine.

Get Configuration Data with a Managed Service

Here is my ConfigUpdater class
private final class ConfigUpdater implements ManagedService {
#SuppressWarnings("rawtypes")
#Override
public void updated(Dictionary config) throws ConfigurationException {
if (config == null) {
return;
}
String title = ((String)config.get("title"));
}
}
My question is how can I access String title in any other class? Or how can I get config dictionary in any other class... Method updated will only be called when a config file is changed... once it is changed how can access its data in other class?
In general you would create a service that exposes these properties to other components.
For example, you could give your ConfigUpdater a second interface. Another component can than lookup/inject this interface from the service registry and use it's methods to access the properties.
I created an example project on GitHub: https://github.com/paulbakker/configuration-example
The most important part is the service that implements both ManagedService and a custom interface:
#Component(properties=#Property(name=Constants.SERVICE_PID, value="example.configurationservice"))
public class ConfigurationUpdater implements ManagedService, MyConfiguration{
private volatile String message;
#Override
public void updated(#SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
message = (String)properties.get("message");
}
#Override
public String getMessage() {
return message;
}
}
The configuration can then be used like this:
#Component(provides=ExampleConsumer.class,
properties= {
#Property(name = CommandProcessor.COMMAND_SCOPE, value = "example"),
#Property(name = CommandProcessor.COMMAND_FUNCTION, values = {"showMessage"}) })
public class ExampleConsumer {
#ServiceDependency
private volatile MyConfiguration config;
public void showMessage() {
String message = config.getMessage();
System.out.println(message);
}
}

Resources