RIFE 1.4 has implemented a mechanism that is able to add metadata to POJOs via the classloader without modifying the POJOs themselves. This feature is automatic, transparent as long as your POJO is on the classpath.
Adding constraints is as easy as creating a class in the same package as the POJO, naming it with a MetaData suffix and using provided extension points. Using this standardized naming convention makes its purpose clear and also allows the classloader to find and associate your metadata class with your POJO.
The magic happens when the classloader finds both. It uses bytecode engineering to dynamically merge the metadata class into the POJO, and implements all interfaces of the medata class through delegation.
The simplest example of such metadata would look something like the following,
public class Pet {
private String name;
public void setName(String name) { this.name = name; }
public String getName() { return this.name; }
}
public class PetMetaData extends MetaData {
public void activateMetaData() {
addConstraint(new ConstrainedProperty("name")
.maxLength(10)
.notNull(true));
}
}
Putting this all together (and under the control of the classloader) allows you to access the Pet class' other aspects simply by casting it to the other interfaces. Here you're casting to Validated, an interface providing a way to validate against some given constraints (and provided through MetaData),
Pet pet = new Pet();
Validated validated = (Validated)pet;
assertFalse(validated.validate());
validated.resetValidation();
person.setName("Dogbert");
assertTrue(validated.validate());
Of course, the classloader needs to be enabled for all the magic to work. In a standalone environment this is as simple as,
java com.uwyn.rife.test.RunWithEngineClassLoader YourMainClass
In enterprise environments, say within JBoss or Tomcat, the RIFE classloader is enabled by default and the preferred manner to make use of it is as a simple filter from web.xml,
<filter>
<filter-name>RIFE</filter-name>
<filter-class>com.uwyn.rife.servlet.RifeFilter</filter-class>
<init-param>
<param-name>rep.path</param-name>
<param-value>rep/participants.xml</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>RIFE</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Like other tools using bytecode engineering (Hibernate, Spring, etc) RIFE's approach come at a price. Sun's security manager doesn't permit such use by default and it's not surprising that under many application servers (whether enabled by default or not) ends up being disabled.
As this is the first release of this metadata approach, the RIFE project plans on further exploring its capabilities; such as the possibility of providing dynamic Active Record capabilities in plain Java based on existing database table structures.
What do you think of this direction? Is it worthwhile stretching the capabilities of Java in such a fashion?
-
Transparently adding metadata to POJOs (9 messages)
- Posted by: Steph Meslin-Weber
- Posted on: March 06 2006 10:08 EST
Threaded Messages (9)
- Transparently adding metadata to POJOs by David Tauzell on March 07 2006 11:53 EST
- Transparently adding metadata to POJOs by Geert Bevin on March 07 2006 12:03 EST
- Transparently adding metadata to POJOs by Adrian Colyer on March 07 2006 12:57 EST
- Transparently adding metadata to POJOs by Geert Bevin on March 07 2006 13:09 EST
- Nice but please use real Java code for constraints by Carl Rosenberger on March 07 2006 15:15 EST
- Nice but please use real Java code for constraints by Geert Bevin on March 07 2006 15:41 EST
-
Nice but please use real Java code for constraints by Carl Rosenberger on March 07 2006 04:28 EST
- Nice but please use real Java code for constraints by Geert Bevin on March 07 2006 05:56 EST
-
Nice but please use real Java code for constraints by Carl Rosenberger on March 07 2006 04:28 EST
- Nice but please use real Java code for constraints by Geert Bevin on March 07 2006 15:41 EST
- Why? by George Petrov on March 09 2006 17:55 EST
-
Transparently adding metadata to POJOs[ Go to top ]
- Posted by: David Tauzell
- Posted on: March 07 2006 11:53 EST
- in response to Steph Meslin-Weber
The classloader/bytecode manipulation is cool, but couldn't you just do:
Validated validated = Validator.getValidated(pet); -
Transparently adding metadata to POJOs[ Go to top ]
- Posted by: Geert Bevin
- Posted on: March 07 2006 12:03 EST
- in response to David Tauzell
Hi Dave,
hmm it seems that the example has been chosen by Steph with RIFE's validation system in mind. It is a bit different than others in that it collects validation errors and invalid subjects in a Validated object instance. It's very convenient to be able to keep this together with the model that's actually being validated. Traditionally we did this by either extending a base class or implementing interfaces and delegate. With this new meta data approach however both can cleanly be dissociated while still having stateful delegation by associating an instance of the meta data class with one particular instance of the model.
Hope this clarifies this a bit.
Best regards,
Geert -
Transparently adding metadata to POJOs[ Go to top ]
- Posted by: Adrian Colyer
- Posted on: March 07 2006 12:57 EST
- in response to Steph Meslin-Weber
I don't pretend to fully understand what your MetaData supertype does, but in AspectJ if you wanted to make Pet support Validation as a separate aspect, you'd write this:
public aspect PetValidationSupport {
declare parents : Pet implements Validated;
public boolean Pet.validate() {
// validation implementation...
}
// other methods of Validated defined for Pet, or inherited via
// AspectJ-defined default implementation
}
if this aspect is on the classpath, and you're using AspectJ's load-time weaving, you're done. (AspectJ supports Java 5 javaagent, JRockit agent, classloader-based weaving etc..). It comes with good tool support, a battle-hardened implementation, ability to do more than just meta-data etc..
So modular implementation of validation interfaces etc. certainly has some merit - but you could get there a lot faster by piggy-backing on AspectJ rather than rolling your own bytecode transformation tool....
(and you can always use the @AspectJ annotations if you don't like the aspectj keywords) -
Transparently adding metadata to POJOs[ Go to top ]
- Posted by: Geert Bevin
- Posted on: March 07 2006 13:09 EST
- in response to Adrian Colyer
The MetaData super type is not needed, the only thing that actually needs to be present is the MetaDataMerged interface. This will make the base class automatically implement the interfaces of the meta data class through delegation. I have not much experience with AspectJ, but afaik, what you describe adds method implementations, but doesn't delegate to a dedicated meta data instance. This requires additional handling and byte-code rewriting when cloning is performed for instance, as both the base instance and the meta data instance need to be correctly associated in the clones.
Also, our approach doesn't need any tool support or special keywords. It's all pure Java and the only 'magic' for the user is the MetaData suffix on the class name. This makes it very intuitive and easy to use and comprehend. -
Nice but please use real Java code for constraints[ Go to top ]
- Posted by: Carl Rosenberger
- Posted on: March 07 2006 15:15 EST
- in response to Steph Meslin-Weber
The simplest example of such metadata would look something like the following,
public class Pet {
private String name;
public void setName(String name) { this.name = name; }
public String getName() { return this.name; }
}
public class PetMetaData extends MetaData {
public void activateMetaData() {
addConstraint(new ConstrainedProperty("name")
.maxLength(10)
.notNull(true));
}
}
I don't agree. The simplest way to work with Java is to keep field name strings out of code, so it stays refactorable and typesafe. For constraints you would also want to use method calls on fields instead of reinventing a parallel constraint language with methods like #maxLength() or #notNull(). How about this:
public class PetMetaData extends MetaData {
public boolean activateConstraints(Pet pet){
return pet.getName() != null
&& pet.getName().length <= 10;
}
}
The above can also be bytecode-engineered perfectly to be integrated with the original class. We do something similar with Native Queries:
http://www.db4o.com/about/productinformation/whitepapers/#nq -
Nice but please use real Java code for constraints[ Go to top ]
- Posted by: Geert Bevin
- Posted on: March 07 2006 15:41 EST
- in response to Carl Rosenberger
Carl, you can write those if you want. By implementing MetaDataBeanAware, you received the instance of person that it's associated with.
However, you are doing there the real validation, you don't provide meta data that can be interpreted by any kind of layer. MaxLength and NotNull can both be used by database structure creation logic, form building logic, validation logic, ... -
Nice but please use real Java code for constraints[ Go to top ]
- Posted by: Carl Rosenberger
- Posted on: March 07 2006 16:28 EST
- in response to Geert Bevin
MaxLength and NotNull can both be used by database structure creation logic, form building logic, validation logic, ...
Why don't you transform { String#length() } and { Object != null } to database structure creation logic, form building logic and validation logic?
That's exactly what we are doing with Native Queries: We allow users to write plain Java and analyze Java bytecode to be able to execute indexed queries without running the actual code the user writes. -
Nice but please use real Java code for constraints[ Go to top ]
- Posted by: Geert Bevin
- Posted on: March 07 2006 17:56 EST
- in response to Carl Rosenberger
Since these are merely the most simplest of constraints. Adding constraints about the mime type, content transformation, dimension attributes, etc etc are not directly mappable to the Java type of the bean property. For example:
addConstraint(new ConstrainedProperty("imageMedium")
.mimeType(MimeType.IMAGE_JPEG)
.contentAttribute("width", 120)
.editable(false));
addConstraint(new ConstrainedProperty("text")
.mimeType(MimeType.APPLICATION_XHTML)
.autoRetrieved(true)
.fragment(true)
.transformer(transformer)); -
Why?[ Go to top ]
- Posted by: George Petrov
- Posted on: March 09 2006 17:55 EST
- in response to Steph Meslin-Weber
Could you please spare a thought for the people who will have to work on this later. Transparent means invisible.
If this breaks or needs change, you're in trouble. As soon as you have to change it, you are faced with a load of invisible layers of sticky stuff, which will eventually suffocate you. The reaction of the average programmer is to just put a hack somewhere which will seem to work at first glance.
Things like EJBs, ORM tools, etc hide a lot of details from you, but at least they are standard enough for people to dig into the details and familiarise themselves with the oddities. In the case of Hibernate (example given by the poster) it is invisible and will stay so, unless it breaks, and even then the cause of the breakage might be visible. This is not.
In the example given, to achieve validation of a property, on top of writing a new class, one has to consider these - classpaths (in IDE and build files and runtime), classloading mechanism (potentially different between environments and servers), security policy (let's not even start on this), packaging, developer training, maintenance.
Now, compare to this:
void setName(String name) throws ValidationException {
if (!validator.validatePersonsName(name)) {
throw new ValidationFailedException(...);
}
this.name = name;
}
Yes, I know, it's not meant for simple cases like this.... But it's open to abuse all the same, isn't it? And people will abuse it just to prove how clever they are, just like with everything else so far. And how messy can it get in a real complex case? In a real project learning little clevernesses like this _properly_ and applying them _properly_ is well near the bottom of priorities. Most people will be just happy to see it work after days of debugging somebody else's code.
In short, this is not a pragmatic direction to take in my opinion....