Apache Wicket uses so called model objects to bind data objects to Wicket components. The framework provides a number of model implementations to access data objects and their properties in various ways. The PropertyModel implementation for example is used to access and set the value of a Java object using reflection.
EPropertyModel
If you are working with EMF (Eclipse Modeling Framework) generated model classes you can also use PropertyModels to bind structural features (properties in EMF terminology) of an EObject to a Wicket component. E.g. if your EObject has an attribute called “comment” you could instantiate a model like this
IModel model = new PropertyModel(myEObject, "comment");
This approach has several disadvantages. First, the name of the attribute is declared as a String and not as a compile-time reference to the actual attribute. This means the application could break during runtime if you have renamed the attribute at one point and forgot to also change the String value. Second, this property model uses runtime reflection which is a slow alternative to compile time based access.
Java classes generated from EMF Models by default extend the EObject class which provides a compile-time reflection API. Using the generic eGet and eSet methods any public property of the EObject can be accessed. The EMF Generator also generates a Package class for each EMF package that holds a Literals interface listing all available attributes and references of all generated EClasses of the given package. If you lets say have an EClass “Ticket” with the attribute “comment” in the package “com.acme.service” you could utilize the compile time reflection API as follows:
EAttribute commentAttribute = com.acme.service.ServicePackage.Literals.TICKET__COMMENT; Ticket newTicket = com.acme.service.ServiceFactory.eINSTANCE.create(Ticket.class); newTicket.eSet(commentAttribute, "my comment");
Using the EMF compile-time reflection API I created a generic EMF ECore Feature Model implementation that leverages the compile-time references to structural features. Most of the code is actually related to working around the fact that EAttributes and EReferences are not serializable but which they should to be part safely referenced in a model that potentially is serialized by Wicket between requests.
import org.apache.wicket.model.IDetachable; import org.apache.wicket.model.IObjectClassAwareModel; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EStructuralFeature; /** * A property model to bind an attribute of an EMF ECore object * * @author Sebastian Thomschke */ @SuppressWarnings("unchecked") public class EFeatureModel<T> implements IObjectClassAwareModel<T> { private static final long serialVersionUID = 1L; private transient EStructuralFeature feat; private String featContainerClassName; private String featName; private String featPackageNsURI; private EObject target; public EFeatureModel(EObject target, EStructuralFeature efeature) { this.target = target; this.feat = efeature; this.featName = efeature.getName(); this.featContainerClassName = efeature.getContainerClass().getName(); this.featPackageNsURI = ((EPackage) efeature.eContainer().eContainer()).getNsURI(); } public void detach() { if (target instanceof IDetachable) ((IDetachable) target).detach(); } public EStructuralFeature getFeature() { if (feat == null) { EPackage ePackage = EPackage.Registry.INSTANCE.getEPackage(featPackageNsURI); for (Object eClassifierItem : ePackage.getEClassifiers()) { EClassifier eClassifier = (EClassifier) eClassifierItem; if (eClassifier.getInstanceClass().getName().equals(featContainerClassName)) feat = ((EClass) eClassifier).getEStructuralFeature(featName); } } return feat; } public T getObject() { return (T) target.eGet(getFeature()); } public Class<T> getObjectClass() { return getFeature().getEType().getInstanceClass(); } public void setObject(T object) { // do nothing if the object is the same instance that is returned by the getter // this is esp. important for collection properties if (target.eGet(getFeature()) == object) return; target.eSet(getFeature(), object); } @Override public String toString() { return new StringBuilder("Model:classname=[").append(getClass().getName()).append("]").append(":target=[") .append(target).append("]").toString(); } }
Using this new model implementation is as simple as using the PropertyModel provided by Wicket:
IModel model = new EFeatureModel(myTicket, com.acme.service.ServicePackage.Literals.TICKET__COMMENT);
You can now fully enjoy the compile-time safeness of your EMF model.
Making EObjects serializable
Wicket usually serializes components and their associated models between HTTP requests. If you are using the provided EPropertyModel your EObject classes must implement the Serializable interface. To achieve this, simply extend the EObject interface with the Serializables interface and set “Root Extends Interface” property in your genmodel configuration to this interface.
/** * @author Sebastian Thomschke */ public interface SerializableEObject extends EObject, Serializable { }
When you now regenerate the model classes they will extend the SerializableEObject interface and Wicket will stop complaining about objects in the model being not serializable.
Interesting the EMF capabilities for generating compile time reflection, this is one argument more to consider Eclipse Modeling as a suitable development platform. I’ve been reading about it as DSL workbench lately and I think it is worth it.
Cheers,
Tony.