myfaces-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Apache Wiki <wikidi...@apache.org>
Subject [Myfaces Wiki] Update of "Code Generation" by SimonKitching
Date Thu, 31 Jan 2008 21:40:02 GMT
Dear Wiki user,

You have subscribed to a wiki page or wiki category on "Myfaces Wiki" for change notification.

The following page has been changed by SimonKitching:
http://wiki.apache.org/myfaces/Code_Generation

The comment on the change is:
document mail thread on code generation

New page:
= Code Generation in MyFaces =

== The Issues ==

In JSF, there are a number of classes and configuration files that need to be kept synchronized;
a change to one requires updates to other artifacts. In addition, there are a number of places
where extremely repetitive, "boilerplate" code or configuration is needed.

Keeping the files in sync manually is a lot of work, and it is easy to make mistakes. There
have therefore been a number of attempts to simplify things; this page documents the various
approaches and some of the proposals for the future.

== The Artifacts ==

The artifacts in question are:
 * UIComponent subclasses
 * Converter subclasses
 * Validator subclasses
 * JSP tag handler classes
 * jsp .tld taglib configuration files
 * the faces-config.xml file
 * the facelets configuration file
 * facelets tag handler classes??

Each UIComponent class typically has a number of "jsf" properties, requiring getter and getter
methods. The implementation of these methods is almost identical for each property. Each UIComponent
class also needs saveState and restoreState methods that are very simple, and vary only due
to the set of properties available on the component class. Preferably, comments need to be
present on the class and the properties that are similar or identical to the documentation
available in the jsp taglib files.

?? converters ??

?? Validators ??

A jsp taghandler class must exist for each UIComponent. In most cases these classes are extremely
repetitive to code and simply create a component and copy values from the jsp tag onto the
component. The taghandler must have a property (setter/getter) for each "jsf" property on
the corresponding component class.

One or more jsp taglib files needs to be created, with an entry for each jsp taghandler class,
and nested attribute entries for each "jsf property" of each taghandler class. Ideally, documentation
needs to be present on each tag entry and each attribute entry. Due to limitations in the
taglib format, even when two uicomponent classes are subclasses of the same base, the configuration
for the attributes they inherit from the shared parent must be duplicated.

The faces-config.xml file needs to declare each component, each converter and each validator,
and map components to renderers.

For facelets support, a facelets configuration file is needed which is similar to the faces-config.xml
file.

?? are facelets tag handler classes needed ??

== Previous Solutions ==


=== Current myfaces core (1.1.5) ===

!MyFaces Core 1.1.5 and a few preceding releases simply keep things in sync manually. Adding
an attribute to a component means writing the setters and getters on the uicomponent, doing
the same on the taghandler, then updating the taglib files. Adding a component means creating
uicomponent and taglib classes, ensuring all the datatypes match, updating faces-config.xml
etc. However myfaces core is an implementation of the jsf spec, so now that the spec has been
completely implemented, components do not get added or their apis modified so this is less
of an issue. Documentation improvements are still somewhat clumsy as they may need to be done
both in the jsp taglibs and the relevant .java file.

Facelets config files are maintained by hand too, outside of the myfaces core project.

To avoid the horrible duplication of entries in .tld files, there is a simple xsl-stylesheet-based
solution to allow shared attribute definitions to be referenced from the tld entries for each
component. 

=== Current Tomahawk (1.1.6) ===

This works just like myfaces core 1.1.5, ie everything done by hand. But, unlike core, new
components, validators and converters do regularly get added to tomahawk and component apis
do change. The effort to keep all this in sync is therefore significant.

Again, facelets support is currently managed outside the project; in this case, via pages
maintained on the wiki.

=== Older Myfaces Core 1.x ===

At one time, a code generator was build for myfaces core 1.x. The approach was for xml configuration
files to contain meta-data about what jsf properties each component had. Each component class
and taglib class (checked in to svn) had special markers like
{{ === BEGIN GENERATED CODE ===}} and {{ === END GENERATED CODE ===}}. When any jsf property
changed, an ant task could be run which would erase all code within the marked blocks, and
then regenerate it using the (new) metadata; the java source code was modified "in place",
and the result was then checked back in to svn. Presumably taglib and faces-config files could
also be generated(?).

The result was that code checked out of svn was always complete, compilable, normal code -
except for these special comments, within which code was not editable (or at least would be
wiped out next time code was regenerated). Bugfixes would go directly into these classes,
but api changes must be made in the metadata files instead, and the code-genration

This code eventually fell into disuse, and people started making manual changes to the "generated"
sections. This code generation approach was eventually abandoned, more through disinterest
and lack of knowledge than any deliberate decision.

One of the last developers to work on the old code-generation framework commented that it
was "very painful". Not sure whether this comment was about the basic concept of this approach,
or just the implementation.

=== Trinidad ===

The trinidad project, based on code written at Oracle, created a build process in which extensive
metadata is stored in xml config files. Template files, resembling java but with special markers,
are checked in to a "templates" directory. A processing step then merges the metadata into
the templates to generate complete classes. The resulting compilable java classes are *not*
checked in to svn.

The "processing step" was initially an Ant task, later became a maven1 plugin, and then a
maven2 plugin (called trinidad-faces-plugin). 

This process has been used for all trinidad releases (both 1.1.x and 1.2.x) to the current
date.

The build plugin does have the ability to skip the code-generation for some classes, allowing
specific ones to be hand-written.

=== Myfaces core 1.2.0 and 1.2.2 ===

When the myfaces core 1.2.x series was branched and developed, the existing trinidad component
build system was adopted. The old myfaces core component classes became templates, and metadata
files were created to define their properties. The corresponding hand-written code was removed
from the template files, as it was then generated instead.

=== Tobago ===

Tobago was mentioned only very briefly during the discussions. No-one seemed to know how Tobago
manages this issue.

== Further Discussions ==

During discussions about the future release plans for Tomahawk, significant debate occurred
about the best way to address the issues listed at the top of this page. The idea of adopting
the current trinidad/myfaces-1.2.x system did not meet with universal approval, and this page
attempts to list the main points made during those discussions.

The discussion thread commenced on myfaces-dev on 30 Jan 2008.

=== The Trinidad approach ===

Some discussion occurred about how Trinidad dealt with these issues. There was quite a bit
of misunderstanding, however.

Hopefully a trinidad developer can fill in this section properly.

Trinidad-faces-plugin input files are modelled on the idea of a "mini faces config file".
It appears that trinidad has quite a deep "class hierarchy" of templates, ie templates that
generate abstract classes, and other templates that then generate subclasses of those generated
abstract classes. 

Some component classes do have significant logic internally, ie they are not just dumb data-structures.
This logic is in the templates, ie does not become "real" java code until after the code-generation
step.

=== Generating base classes ===

It was suggested that code-generation for component classes could just generate an abstract
parent class, and that a "real" component class would then subclass it. The parent would never
be modified by developers, and the child class would never be modified by code-generation.


It was not discussed whether the parent class would be checked in or not.

This does allow code generation to insert package-scope variables and methods into the generated
class, which the concrete child would have access to due to being in the same package. These
vars/methods would not be visible to subclasses, however, so would not "pollute" the api.

One drawback is that this generated class is visible in the public ancestry of the concrete
class, distorting the "clean" hierarchy. An opinion was expressed that this was really very
ugly.

And this approach is not possible for uicomponent classes defined in the standard as these
have defined hierarchies that cannot be modified.

This approach may be feasable, however, for libraries like Tomahawk and Trinidad.

Meta-data would of course still be maintained in configuration files of some sort, external
to the generated code.

It was not clear whether proposals were for the concrete classes to be checked in to the source
directory tree as java classes, or checked in as "templates" (although they would be complete
java classes).

If the former, then it would seem that the generated parent classes would need to be written
into the source tree too, otherwise a compile pass over that directory would fail. This does
introduce some problems with things like "svn status" showing lots of unversioned files in
version-controlled directories. However this could simply be lived with (the generated classes
probably would have names that indicate their generated status in some way), or svn-ignore
flags could be set so that these files are not reported by svn. There still is some risk,
however, of these generated classes accidentally getting checked in.

If the concrete child classes are instead kept separate from the normal source dirs, then
both they and their generated parents could be written by the build process into "generated
source" directories. This avoids cluttering the "real" source directories with generated code,
but does mean that:
 * editing the child classes will not have IDE tool support, as they are not "source" classes,
and
 * the IDE must be told about the "generated source" directory

With this approach, there is also the issue that a concrete class needs to be hand-created
and checked in, even if there are actually no customisations needed. Possibly a tool could
detect this case, though, and create the concrete class definition too. 

=== An annotation-driven approach ===

It was suggested that the component, converter and validator classes should be written as
normal java classes, ie in the manual style currently used for myfaces 1.1.x and tomahawk.
However some kind of annotations (whether java1.5 annotations, xdoclet tags, or other) would
be used to mark parts of the class. These classes would be checked in to svn as normal.

A processing tool would then scan the source, and use the annotations to drive the generation
of jsp taghandlers, tld files, faces-config files, facelets config files, etc. No external
meta-data files would be kept; all information necessary to generate taghandlers and the jsf
configuration files would be in the source code files instead.

This does require more manual coding than the trinidad-faces-plugin approach. However it does
also mean that all code checked in to svn will compile immediately, without requiring any
processing steps (no code in the core jsf classes depend upon the existence of taglib classes
or config files).

Debugging and bugfixing should be simplified with this approach, as there are no special generated-source
directories that IDEs need to be told about, and the source to be fixed is "normal", not template
code that is not actually a real class. Those classes that would be generated (jsp taghandlers)
are very seldom of interest for debugging or development.

Open questions were whether
 a. writing the property getter/setters is too much work, and
 b. how saveState/restoreState can be sensibly done without code generation

Some people expressed the opinion that (a) was not a significant issue.

Item (b) is indeed an issue, and would need to be addressed somehow. The implementation of
save/restore methods is very simple, but does require listing all the jsf properties of the
class correctly, and in the same order in each method; manual implementation of these seemed
to be generally viewed as undesirable. One possibility is to move towards the Trinidad state-saving
approach in which state is "externalised" and the save/restore methods are much simpler.

Speculation also occurred over whether reflection, or code-generated "helper" classes could
be used to implement save/restore methods. Nothing conclusive came of this though, except
that the obvious solution had catches. Using private variable access via reflection is probably
not reliable due to classloader security constraints, etc.

A question was raised about why state isn't stored in the attributes map, which is already
automatically saved and restored.

=== Going back to the old Myfaces Core generation approach ===

Some interest was expressed in going back to the original code-generation approach, where
classes were checked in to the normal source dirs, with special markers. A build process could
be run which used metadata files to modify the marked bits of source files in place, and the
results checked back in again. This process would only need to be run when config is changed.

Others expressed concern that metadata and source would get out of sync if the code-regeneration
process was not part of every build cycle.

A number of people thought that this approach was just inelegant.

It was pointed out that if generated classes are checked in, and then the original configuration
referencing that file is removed, the checked in file will remain unless manually removed.

A somewhat more sophisticated variant on the original design was suggested, where the code
markers look something like java1.5 annotations rather than just simple BEGIN and END strings,
and that a java source-code parsing library be used to properly analyse the input and do the
insertion of generated code into the java file in a smarter way. This proposal came with some
sample code that is too long to embed here directly.

Links to java source parsers:
 * http://java-source.net/open-source/parser-generators
 * http://ws.apache.org/jaxme/js/jparser.html


=== Using Templates to define cross-component features ===

It was pointed out that templates can be used to define behaviour that applies to sets of
components even when they do not share a common ancestor. One example is the Tomahawk "forceId"
feature. Components that should share a feature can just share a template, causing them to
get copies of the same code.

This isn't infinitely flexible, however; a component can have only one template so it gets
all of the code from that template or none (eg if the template implements forceId and enabledOnUserInRole,
it cannot get one without the other. It is possible to use a hierarchy of templates to define
a hierarchy of "real" ancestor classes which can give some kind of control over this, but
as java is a single-inheritance language this approach cannot really provide "mixin" functionality.

One reply commented that this looks a lot like aspect-oriented-programming, and that if it
is needed then maybe a real AOP tool should be used.


=== Other notes ===

Some interest was expressed in passing about making jsf component setters/getters use java5
generics.

It was pointed out that the trinidad-faces-plugin already has the ability to write its generated
classes to anywhere that is desired, so writing to "source" or "target" directory is no problem
technically.

One of the high priorities is not creating a barrier to entry for new developers, ie keeping
things simple for people who first start dipping into the code.

Questions were asked about the maven2 eclipse:eclipse goal, and whether it automatically adds
generated-source directories as source folders to an eclipse project. The answer is yes, but
some people appear to have had problems with this.

Some frustration was expressed with the existing "shared" code system, which also resembles
code-generation, and that this makes debugging and related tasks awkward. The concern was
that code-generation as done by trinidad-maven-plugin would create similar (but even more
extensive) problems.

Mime
View raw message