Hi!
We had the problem to produce lazy loaded, session scoped domain models.
Our models are produced by Spring in OSGi bundles.
I want to share our solution.
Here an example model:
package com.imilia.test.model;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.imilia.test.domain.ModelObject;
import com.imilia.test.domain.Person;
import com.imilia.test.interfaces.model.IPersonModel;
import com.imilia.test.interfaces.services.IPersonService;
@Component
@Scope("prototype")
public class PersonModel extends ModelObject implements IPersonModel {
private Person person;
@Autowired
private IPersonService personService;
@Override
public void add() {
firePropertyChange("person", this.person, this.person = personService.newPerson());
}
@Override
public void delete() {
Person nextPerson = personService.nextPerson(person);
personService.deletePerson(person);
firePropertyChange("person", this.person, this.person = nextPerson);
}
@Override
public void load(int oid) {
firePropertyChange("person", this.person, this.person = personService.getPerson(oid));
}
@Override
public Person getPerson() {
return person;
}
@Override
public void next() {
firePropertyChange("person", this.person, this.person = personService.nextPerson(person));
}
@Override
public void previous() {
firePropertyChange("person", this.person, this.person = personService.previousPerson(person));
}
@Override
public void save() {
personService.savePerson(person);
}
}
As you can see the scope is "prototype", because Spring is not able to produce "session" scoped beans in OSGi. (Better said it's not that easy to produce "session" scoped beans in OSGi. Or is there an easy way?)
Ok, that was the model. Here is the serviceinterface and the service:
package com.imilia.test.interfaces.model;
import java.util.Arrays;
import java.util.List;
public interface IModelService {
public static final String PERSONMODEL_MAINPART = "PersonModel_MainPart";
public static final String PERSONMODEL_MAINPART2 = "PersonModel_MainPart2";
public static final List<String> models = Arrays.asList(PERSONMODEL_MAINPART,PERSONMODEL_MAINPART2);
Object getModel(String modelname);
}
package com.imilia.test.model;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import com.imilia.test.interfaces.model.IModelService;
@Service
public class ModelService implements ApplicationContextAware, IModelService {
static final private Map<String,String> modelMap = new HashMap<String,String>();
private ApplicationContext applicationContext = null;
static {
modelMap.put(IModelService.PERSONMODEL_MAINPART, "personModel");
modelMap.put(IModelService.PERSONMODEL_MAINPART2, "personModel");
}
@Override
public Object getModel(String modelname) {
return applicationContext.getBean(modelMap.get(modelname));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
}
The interesting part here is the map. It allows you to produce different models of the same type. I will come back to this soon.
Here are the spring config files from /META-INF/spring:
Module-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.imilia.test.model" />
</beans>
Nothing special. Just Java-based configuration via annotations (as you can in the 2 classes above (@Component, @Service, ...))
osgi-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi http://www.springframework.org/schema/osgi/spring-osgi.xsd"
xmlns:osgi="http://www.springframework.org/schema/osgi">
<osgi:reference id="personService" interface="com.imilia.test.interfaces.services.IPersonService" />
<osgi:service ref="modelService" interface="com.imilia.test.interfaces.model.IModelService"/>
</beans>
personService is the service which accesses the database. It comes from another bundle.
More important is the export of the modelService here.
Ok, that was the "model-bundle".
Now RAP.
I discovered the beautiful mechanism of addons in RAP.
So this is the core idea, a addon which registers the models:
package com.imilia.test.gui;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.eclipse.e4.core.contexts.ContextFunction;
import org.eclipse.e4.core.contexts.IEclipseContext;
import com.imilia.test.interfaces.model.IModelService;
public class DomainModelServiceAddon {
@Inject
IModelService modelService;
@PostConstruct
public void init(IEclipseContext context) {
for (String modelname : IModelService.models) {
context.set(modelname, new ModelContextFunction());
}
}
private class ModelContextFunction extends ContextFunction {
private Object model;
public Object compute(IEclipseContext context, String contextKey) {
if (model == null) {
model = modelService.getModel(contextKey);
}
return model;
}
}
}
Easy code, but mighty (in my opinion )
(It's really astonishing how easy it is to get the modelService from the OSGi-context!)
The idea is to register a factory(ModelContextFunction) in the rap-context instead of the model itself. So you got this lazy loading effect. (Our application will use hundreds of models. Would be a waste of resources and time to load them all at the beginning of a session.
The second idea is trivial: "Get the model if it's empty and store it. Use the stored one, if it's not empty." So you got "session-scope".
OK, but what is the sense?
Here is a part of a view:
...
@Inject @Named(IModelService.PERSONMODEL_MAINPART) private IPersonModel personModel;
private Label lblOid;
private Label lblVorname;
private Label lblNachname;
private Button btnSpeichern;
@PostConstruct
public void init(Composite parent) {
personModel.load(0);
parent.setLayout(new FormLayout());
Now you can see how mighty RAPs DI-context is.
Just one line of code in the view and the domain-model is there.
(Lazy loaded, session scoped)
Now the use of the map in the service becomes clearer.
1. With @Named(IModelService.PERSONMODEL_MAINPART) a model of type "PersonModel" is loaded. If you use the same command in another view. The same model is loaded. (The same session scoped instance).
2. With @Named(IModelService.PERSONMODEL_MAINPART2) a model of type "PersonModel" is loaded. But another instance.
Hope that was a little bit helpful for you.
Comments are welcomed.
Greetings!
Thorsten
[Updated on: Thu, 22 May 2014 19:43]
Report message to a moderator