The "beanification" I am presenting here -- which could in fact be called the "DGX's Java steroided big-brother" -- is the functionality that enables writing EL for the JSP/JSTL Tridion templates, part of the Java Mediator I am currently working on. I knew writing this piece would be fun, but I underestimated how much fun it would really be. I had a blast! :) <geek sneers here/>
EL simplifies writing rather complex expressions by providing the possibility of writing the expression just using the . and [ ] syntax. The dot (.) is used to specify 'Java Beans'-like properties on a given object, and the square brakets ([ ]) specify an indexed element on any property that returns a collection.
Example:
The Tridion beanification, in fact an EL Evaluator, conforms closely to the Java Beans convention, with minor modifications to suit the JNI proxy methods from .NET into Java. If we wanted to evaluate ${base.propertyName}, default Java Bean standard would look up, using reflection, the methods getPropertyName() and isPropertyName() on the base object. Additionally, the Tridion EL evaluator will also lookup GetPropertyName(), IsPropertyName() and PropertyName() methods on the base object, that would be generated from .NET methods or properties.
Special handling is done for items in the Package and Engine -- expressions starting with Package or Engine will consider them as the base object in the EL.
Example:
For all other expressions, the evaluator will try to first look it up in the Package and if found, try to retrieve its actual TOM.Java object (from the Engine), so expressions ${Engine.Page} and ${Page} are in fact the same, as both yield the same Page TOM.Java proxy object into .NET.
Some Tridion EL examples:
EL simplifies writing rather complex expressions by providing the possibility of writing the expression just using the . and [ ] syntax. The dot (.) is used to specify 'Java Beans'-like properties on a given object, and the square brakets ([ ]) specify an indexed element on any property that returns a collection.
Example:
${Page.ComponentPresentations[1]}
looks up the Page object, then accesses the ComponentPresentations list property on it and finally returns the 2nd Component Presentation as denoted by [1].The Tridion beanification, in fact an EL Evaluator, conforms closely to the Java Beans convention, with minor modifications to suit the JNI proxy methods from .NET into Java. If we wanted to evaluate ${base.propertyName}, default Java Bean standard would look up, using reflection, the methods getPropertyName() and isPropertyName() on the base object. Additionally, the Tridion EL evaluator will also lookup GetPropertyName(), IsPropertyName() and PropertyName() methods on the base object, that would be generated from .NET methods or properties.
Special handling is done for items in the Package and Engine -- expressions starting with Package or Engine will consider them as the base object in the EL.
Example:
${Engine.RenderMode} returns the Render Mode as string by invoking engine.getRenderMode()
${Package.Page.PublishLocationUrl} returns the URL where the page is published by invoking engine.GetObject(package.GetByName("Page")).GetPublishLocationUrl()
For all other expressions, the evaluator will try to first look it up in the Package and if found, try to retrieve its actual TOM.Java object (from the Engine), so expressions ${Engine.Page} and ${Page} are in fact the same, as both yield the same Page TOM.Java proxy object into .NET.
Some Tridion EL examples:
${Page.ContextRepository.Title}
${Page.Publication.Title}
${Page.Publication.Metadata.Link.Metadata.Type.Title} from current Page get Publication (ContextRepository), get Metadata Schema, field Link (Component Link) get actual linked Component, get Metadata field Type (a Category Keyword) and finally get the Keyword title;
${Page.Publication.Title}
${Page.Publication.Metadata.Link.Metadata.Type.Title} from current Page get Publication (ContextRepository), get Metadata Schema, field Link (Component Link) get actual linked Component, get Metadata field Type (a Category Keyword) and finally get the Keyword title;
${Page.Publication.Metadata.Link.Fields.Summary} linked Component, field Summary
${Page.Publication.Metadata.Embeddable[1].Number} Embeddable field, numeric field Number
${Page.Publication.Metadata.Embeddable[1].MMLink.Title} Embeddable Multimedia Link, get title
${Page.Publication.Metadata.Link.Title} Component Link title
I can even write loops now:
<c:forEach var="cp" items="${Page.ComponentPresentationsList}">
Component: ${cp.Component.Id} '${cp.Component.Title}'
Component Template: ${cp.ComponentTemplate.Id} '${cp.ComponentTemplate.Title}'
</c:forEach>
Component: ${cp.Component.Id} '${cp.Component.Title}'
Component Template: ${cp.ComponentTemplate.Id} '${cp.ComponentTemplate.Title}'
</c:forEach>
Notice the usage of ComponentPresentationsList instead of ComponentPresentations -- this is because the List version returns a java.util.List wrapper, so it can be used in a for each iteration.
Implementation Details
I implemented the whole logic in an javax.el.ELResolver class, which gets wired up in the Page Context's getELContext method.
public class TridionELResolver extends ELResolver {
public class TridionELResolver extends ELResolver {
private static ELResolver instance;
private static final
String methodPrefixes[] = new String[] { "get",
"Get", "is",
"Is", ""
};
@Override
public
Object getValue(ELContext context, Object base, Object property) throws NullPointerException,
PropertyNotFoundException,
ELException {
Object result = null;
if
(base == null) {
result =
getTopLevelProperty(context, property);
if
(result != null && !(result instanceof Item)) {
context.setPropertyResolved(true);
}
} else
{
result = getBaseProperty(context,
base, property);
if
(result == null) {
String stringProperty =
property.toString();
String friendlyProperty =
fixFriendlyProperty(base, stringProperty);
if
(!friendlyProperty.equals(stringProperty)) {
result =
getBaseProperty(context, base, friendlyProperty);
}
}
if
(result != null) {
context.setPropertyResolved(true);
}
}
return
result;
}
public static ELResolver getInstance() {
if
(instance == null) {
instance
= new TridionELResolver();
}
return instance;
}
private
Object getTopLevelProperty(ELContext context, Object property) {
FakePageContext pageContext =
(FakePageContext) context.getContext(JspContext.class);
Package _package =
pageContext.getPackage();
Engine engine =
pageContext.getEngine();
String propertyName =
property.toString();
Object result = null;
if
(propertyName.equals("Engine")) {
result = engine;
} else if (propertyName.equals("Package")) {
result = _package;
} else
{
result =
getEngineOrPackageProperty(engine, _package, property);
}
return
result;
}
private
Object getEngineOrPackageProperty(ELContext context, Object property) {
FakePageContext pageContext =
(FakePageContext) context.getContext(JspContext.class);
Package _package =
pageContext.getPackage();
Engine engine =
pageContext.getEngine();
return
getEngineOrPackageProperty(engine, _package, property);
}
private
Object getEngineOrPackageProperty(Engine engine, Package _package, Object
property) {
Object result =
getPackageProperty(_package, property);
if
(result != null) {
Object engineProperty =
getEngineProperty(engine, result);
if
(engineProperty != null) {
result = engineProperty;
}
}
return
result;
}
private
Object getEngineProperty(Engine engine, Object property) {
Object
result = null;
if
(property instanceof Item) {
Item item = (Item) property;
String id = item.GetValue("ID");
if
(id == null) {
return result;
}
result = engine.GetObject(item);
} else
{
String stringProperty =
property.toString();
if
(TcmUri.IsValid(stringProperty) || stringProperty.startsWith("/webdav")) {
result =
engine.GetObject(stringProperty);
}
}
return
result;
}
private
Object getPackageProperty(Package _package, Object property) {
Item item =
_package.GetByName(property.toString());
return
item;
}
private
Object getBaseProperty(ELContext context, Object base, Object property) {
Object
result = null;
String propertyName =
property.toString();
if
(base instanceof Component) {
result = getComponentProperty(base,
propertyName);
} else if (base instanceof
RepositoryLocalObject) {
result =
getRepositoryLocalObjectProperty(base, propertyName);
} else if (base instanceof
Repository) {
result =
getRepositoryProperty(base, propertyName);
} else if (base instanceof
Package) {
result = getEngineOrPackageProperty(context,
propertyName);
} else if (base instanceof
ItemFields) {
Object[] bases = new Object[] { base };
result =
getItemFieldsProperty(bases, propertyName);
base = bases[0];
} else if (base instanceof
List) {
Object[] bases = new Object[] { base };
result = getListProperty(bases,
property);
base = bases[0];
} else if (base instanceof
IList) {
Object[] bases = new Object[] { base };
result = getIListProperty(bases,
property);
base = bases[0];
}
if
(result == null) {
result = getBeanProperty(base,
propertyName);
}
return
result;
}
private
Object getComponentProperty(Object base, String propertyName) {
Object result = null;
Component component = (Component) base;
if
(propertyName.equals("Fields")) {
result = new ItemFields(component.getContent(),
component.getSchema());
} else if (propertyName.equals("Metadata")) {
result = new ItemFields(component.getMetadata(),
component.getMetadataSchema());
}
return
result;
}
private
Object getRepositoryLocalObjectProperty(Object base, String propertyName) {
Object result = null;
RepositoryLocalObject localObject =
(RepositoryLocalObject) base;
if
(propertyName.equals("Metadata"))
{
result = new ItemFields(localObject.getMetadata(),
localObject.getMetadataSchema());
}
return
result;
}
private
Object getRepositoryProperty(Object base, String propertyName) {
Object result = null;
Repository repository = (Repository)
base;
if
(propertyName.equals("Metadata"))
{
result = new ItemFields(repository.getMetadata(),
repository.getMetadataSchema());
}
return result;
}
private
Object getItemFieldsProperty(Object[] bases, String propertyName) {
Object result = null;
Object base = bases[0];
ItemFields itemFields = (ItemFields)
base;
base = bases[0] =
itemFields.getItem(propertyName);
ItemField itemField = (ItemField) base;
if
(itemField.getDefinition().getMaxOccurs() == 1) { // single-value
result = getBeanProperty(base, "Value");
} else
{ // multi-value
result = getBeanProperty(base, "ValuesList");
if
(result == null) {
result = getBeanProperty(base, "Values");
}
}
if
(itemField.getDefinition().getMinOccurs() == 0 && result == null) { // optional
result = "";
}
return
result;
}
private
Object getListProperty(Object[] bases, Object property) {
Object result = null;
Object base = bases[0];
List<?> list = (List<?>)
base;
int
intProperty = 0;
if
(property instanceof Number) {
Number numberProperty = (Number)
property;
intProperty =
numberProperty.intValue();
result = list.get(intProperty);
} else
{
base = bases[0] =
list.get(intProperty);
}
return
result;
}
private
Object getIListProperty(Object[] bases, Object property) {
Object result = null;
Object base = bases[0];
IList list = (IList) base;
int
intProperty = 0;
if
(property instanceof Number) {
Number numberProperty = (Number)
property;
intProperty =
numberProperty.intValue();
result = list.getItem(intProperty);
} else
{
base = bases[0] =
list.getItem(intProperty);
}
return
result;
}
private
Object getBeanProperty(Object base, String property, Object... methodArguments)
{
Object result = null;
String propertyName = capitalize(property);
for
(String methodPrefix : methodPrefixes) {
String methodName = methodPrefix +
propertyName;
Class<? extends Object> clazz = base.getClass();
Method method = findMethod(clazz,
methodName);
if
(method != null) {
try {
result =
method.invoke(base, methodArguments);
break;
} catch (Exception e) {}
}
}
return
result;
}
private
Method findMethod(Class<? extends
Object> clazz, String methodName) {
for
(Method method : clazz.getMethods()) {
if
(method.getName().equals(methodName)) {
return method;
}
}
return null;
}
private
String fixFriendlyProperty(Object base, String property) {
if
(base instanceof
RepositoryLocalObject) {
if
(property.equals("Publication"))
{
property = "ContextRepository";
}
}
return
property;
}
}
Comments