I set out recently on the quest of creating a Java Mediator. I'm definitely not the first one, but I am surprised how far I got in relatively short time (over the weekend, basically between going to pumpkin patches with my daughters) :)
Ok, so it is still work in progress, but this is what I have:
In the example above, the implementation of executeTemplate(engine, template, _package) is not given. This is the beef of the actual code execution. This method is responsible for:
My skeleton Java class is initially a String and it looks something like this:
The placeholder %s gets formatted (replaced) with the actual Java Fragment template:
I used Reflection API to load the class dynamically:
I created a Java Fragment TBB as shown in the screenshot below. I placed it in a Page Template and then executed it on a Page in TemplateBuilder.
Note: the usage of ContentType -- remember, the ProxyGen limitation on Enums (or any other read-only, const fields)? There is no ContentType.Text available, hence I have to create my own. Behind the scenes, ContentType.Text is in fact a ContentType with mime-type parameter 'text/plain'.
Running the Page Template with the Java Fragment in it in TemplateBuilder will yield the following output:
Note:
Ok, so it is still work in progress, but this is what I have:
- Tom.Java - full API conversion of Tom.Net into C# and Java JNI proxies;
- "Java Fragment" TBB type in the Content Manager;
- Mediator.Java and its counterpart Mediator.Net proxies that are able to dynamically compile a Java Fragment TBB, dynamically load its .class type, and dynamically run its Transform method;
This is still in very early stages of development / productization, but still I'm very proud with what I accomplished in a relative short time.
So how I did it and what I actually accomplish is next...
General Approach
My goal was to mimic the CM approach for .NET templating. Therefore, I wanted to create a Java Fragment TBB that would hold, well... a Java fragment. This TBB would be executed when placed on a Compound Template by a Mediator, so I went ahead and created a normal .NET Tridion Mediator.
The approach is to use a .NET to Java conversion using a code generation tool, similar to JuggerNET. The .NET Mediator would call the Java proxy that would end up in calling the real Java implementation of the mediator. At the same time, the .NET object needed by the mediator would be passed on to the Java counterpart as 'real' objects. The proxy from .NET to Java would take care of the 2-way communication from .NET object to Java proxy and back to .NET object.
Once in Java context, the Java Fragment source code would be 'injected' into a predefined string representing a Java source class. Then the Java source would be compiled into a .class file. Finally the compiled .class would be loaded using reflection and its method executed. This method would accept the input objects Engine and Package, so the Java Fragment would operate on the actual (yet proxied) .NET objects.
Do you Speak Tom.Java?
Since there was a way to do Java to .NET conversion -- this is what Tridion Content Delivery API does using JuggerNET --, there must be a way to do the opposite (i.e. generate Java proxies from an existing .NET DLL). Googling for "calling java from .net" landed me on the jni4net website (http://jni4net.sourceforge.net/), which seemed to do exactly what I wanted. For short, jni4net is a 2-way proxy from Java to .NET to Java (or the other way around .NET to Java to .NET).
The tricky part was to generate these proxies. I used for that the tool that jni4net provides (i.e. ProxyGen). The idea is to feed into the tool either your C# assemblies or Java JARs in order to generate Java JNI proxies for the assemblies and C# proxies for the JARs.
I used as input the Tridion.ContentManager.*.dll from the [Tridion_Home]\bin\client folder. It took me about 2 days to come up with something worthy, due to some quirks of Proxygen, limitations, known issues, my own learning curve, etc. The final result is 2 files:
- Tom.Java.Proxy.dll - the C# counter-part of the proxies required by jni4net;
- Tom.Java.Proxy.jar - the JNI Java proxy classes;
It generated aprox 520 proxy classes from the Tridion DLLs, which represent more or less the entire 'client' Content Manager API. Proxygen has a few limitations, such as it doesn't deal with Generics or does not generate Enums, but other than that is a great tool!
Using Tom.Java one can write calls from .NET into Java passing the Tridion objects as parameters, such as:
C#:
public static void Main(string[] args) {
BridgeSetup bridgeSetup = new BridgeSetup(false) { Verbose = true
};
bridgeSetup.AddClassPath(@"C:\Java
Mediator\ProxyGen\lib\jni4net.j-0.8.7.0.jar");
bridgeSetup.AddClassPath(@"C:\Java
Mediator\Tom.Java\dist\Tom.Java.Proxy.jar");
Bridge.CreateJVM(bridgeSetup);
Bridge.RegisterAssembly(typeof(Engine_).Assembly);
Bridge.RegisterAssembly(typeof(MediatorTest).Assembly);
MediatorTest test = new MediatorTest();
Session session = new Session();
test.testPage(session);
}
Java (implementation of MediatorTest class):
import tridion.contentmanager.Session;
import tridion.contentmanager.TcmUri;
import tridion.contentmanager.communicationmanagement.Page;
public class MediatorTest {
public void testPage(Session session) {
TcmUri
pageUri = new TcmUri("tcm:1-2-64");
Page page =
new Page(pageUri, session);
System.out.println("Page Title:
" + page.getTitle());
}
}
The Mediator Stuff
Once I had Tom.Java generated, writing the Mediator was in fact pretty straight forward. In C#, implement the IMediator interface with its Transform and Configure methods. Then simply call a Java proxy to the 'real' IMediator implementation.
public class JavaMediator : IMediator
{
public void Transform(Engine
engine, Template template, Package package) {
BridgeSetup
bridgeSetup = new BridgeSetup(false) { Verbose = false
};
bridgeSetup.AddClassPath(@"C:\Java
Mediator\ProxyGen\lib\jni4net.j-0.8.7.0.jar");
bridgeSetup.AddClassPath(@"C:\Java
Mediator\Mediator.Java\dist\Mediator.Java.jar");
bridgeSetup.AddClassPath(@"C:\Java
Mediator\Tom.Java\dist\Tom.Java.Proxy.jar");
Bridge.CreateJVM(bridgeSetup);
Bridge.RegisterAssembly(typeof(Engine_).Assembly);
Bridge.RegisterAssembly(typeof(JavaMediatorImpl).Assembly);
IMediator
mediator = new JavaMediatorImpl();
mediator.Transform(engine, template,
package);
}
}
Funny how powerful these proxies are -- my JavaMediatorImpl class (written in Java) actually implements the same IMediator (well, the proxied interface):
import tridion.contentmanager.communicationmanagement.Template;
import tridion.contentmanager.templating.Engine;
import tridion.contentmanager.templating.IMediator;
import tridion.contentmanager.templating.Package;
import tridion.contentmanager.templating.TemplatingLogger;
public class
JavaMediatorImpl implements IMediator {
private
TemplatingLogger log = TemplatingLogger.GetLogger(Engine.typeof());
public void Transform(Engine engine, Template template,
Package _package) {
log.Debug("Start Mediator.Transform");
executeTemplate(engine, template,
_package);
log.Debug("Finish Transform");
}
}
Dynamic Code Execution
- Putting the Java Fragment in a Java source code class context;
- Compiling the Java code from String to .class file;
- Load the .class dynamically and execute, while passing the actual engine and _package proxy object to it;
Put Java Fragment into Class Context
Since Java Fragment TBB is, well... a fragment, it needs to be put in some context in order to execute it. I implemented this context as a 'skeleton' of a class with a placeholder in the middle. This is where the Java Fragment comes in.My skeleton Java class is initially a String and it looks something like this:
private static final String JAVA_FRAGMENT_TBB_PATTERN =
"package mediator;\r\n" +
"\r\n" +
"import
tridion.contentmanager.communicationmanagement.*;\r\n" +
"import
tridion.contentmanager.contentmanagement.*;\r\n" +
"import
tridion.contentmanager.templating.*;\r\n" +
"\r\n" +
"public class JavaFragmentTBB {\r\n" +
"\r\n" +
"
private TemplatingLogger log =
TemplatingLogger.GetLogger(null);\r\n" +
"\r\n" +
"
public void execute(Engine _engine,
tridion.contentmanager.templating.Package _package) {\r\n" +
" Engine
engine = _engine;\r\n" +
" Engine
_e = _engine;\r\n" +
" tridion.contentmanager.templating.Package
_p = _package;\r\n" +
"%s\r\n" +
"
}\r\n" +
"}";
The placeholder %s gets formatted (replaced) with the actual Java Fragment template:
String javaSource
= String.format(JAVA_FRAGMENT_TBB_PATTERN, template.getContent());
SourceStringCompiler
compiler = new SourceStringCompiler("mediator.JavaFragmentTBB", javaSource);
Compile .class File Dynamically
The SourceStringCompiler mentioned above is a class that takes a className and its Java source code as parameters and compiles it into .class file on the file system.
I made use of the Java Compiler API in javax.tools for that.
If the compilation fails, I throw an exception to let the calling code (.NET) know that something was wrong with the actual execution of the Java Fragment. This exception is propagated all the way back into the rendering engine (and it will also show up in TemplateBuilder).
I made use of the Java Compiler API in javax.tools for that.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(null, null, null);
JavaFileObject javaObjectFromString = getJavaFileContentsAsString(className,
javaSource);
Iterable<JavaFileObject> fileObjects = Arrays.asList(javaObjectFromString);
Iterable<String> options = Arrays.asList("-d", "C:\\Java
Mediator\\Classes");
CompilationTask task = compiler.getTask(null, fileManager, null, options, null, fileObjects);
Boolean result = task.call();
if (result) {
log.Debug("Compilation
succeeded");
} else {
log.Error("Compilation
failed\r\n" + message);
throw new RuntimeException("Compilation failed\r\n" + message);
}
I like the simplicity of the code above and the fact that I don't need to deal with creating my own javac execution and batch file. The result of running the CompilationTask.call() is still that the javac is called behind the scenes and that a file .class is generated on the file system.
If the compilation fails, I throw an exception to let the calling code (.NET) know that something was wrong with the actual execution of the Java Fragment. This exception is propagated all the way back into the rendering engine (and it will also show up in TemplateBuilder).
A peculiar thing is about the execution performance -- the first time it takes a few seconds to instantiate the Compiler, but then every subsequent execution (within the same JVM) takes significantly less.
Execute .class Dynamically
Finally, I need to load the .class file dynamically and execute its execute() method with the parameters Engine and Package. Again, the beauty of these JNI proxies is perceivable here -- I am able to pass them in as parameters to this dynamically generated, dynamically compiled class. Still, any modification operated to the Engine and Package objects will be directly reflected back into the 'real' objects in .NET, since we are in fact dealing with proxies to the actual objects.I used Reflection API to load the class dynamically:
File classesDir = new File("C:\\Java Mediator\\Classes");
ClassLoader parentLoader = JavaMediatorImpl.class.getClassLoader();
URLClassLoader myClassLoader = new URLClassLoader(new URL[] { classesDir.toURI().toURL() },
parentLoader);
Class<?> myClass = myClassLoader.loadClass("mediator.JavaFragmentTBB");
Instantiate the class, get the execute() method and call it with Engine and Package object parameters:
Object theInstance = myClass.newInstance();
Method myMethod = myClass.getMethod("execute", Engine.class, Package.class);
myMethod.invoke(theInstance, new Object[] { engine, _package });
It's Show Time, Dim the Lights!
Note: the usage of ContentType -- remember, the ProxyGen limitation on Enums (or any other read-only, const fields)? There is no ContentType.Text available, hence I have to create my own. Behind the scenes, ContentType.Text is in fact a ContentType with mime-type parameter 'text/plain'.
Running the Page Template with the Java Fragment in it in TemplateBuilder will yield the following output:
Note:
- 2 items added to the Package and their mime-types;
- Execution Time is consistently at 0.1sec on every subsequent run (first run was ~6sec). Part of the first time execution time is the instantiation of the JVM inside jni4net, loading the JARs and Assemblies, etc. Interesting to notice is however that I didn't implement any caching -- i.e. the .class file is compiled evey time the TBB executes; also, the new .class is loaded and executed with every single TBB execution;
- Debug information showing in the Output panel;
Known (current) limitation: Page.ComponentPresentations returns IList<ComponentPresentation> and the proxies generated by Proxygen cannot deal with generics. Hence, calling page.getComponentPresentations() on the Java proxy throws a very cryptic InvocationTargetException.
Looking to the (Near) Future
Next steps of Research & Development include:
- support for JAR binary TBBs (I heard some horror stories there) with the ability to run individual pre-compiled "ITemplate" Java classes inside of it;
- JSP/JSTL replacement of the so passionately disliked Dreamweaver TBBs. The idea is to have a JSP-like TBB that accepts JSP/JSTL syntax in order to generate the "Output" package item;
- Code handling for both JSP/JSTL and Java Fragment TBBs (code syntax validation, compilation warning/errors, source code formatting, etc...);
Overall, I'm excited by the potential of this solution. I see it as very cool usage of technologies and it will definitely fill a niche in the market. I shall continue posting updates on this little R&D project. Looking forward to any comments, questions, remarks...
If you liked it so far, check out my next post about Java Fragment validation.
If you liked it so far, check out my next post about Java Fragment validation.
Comments
Do you have this project on Github or somewhere else? I know I can follow it by reproducing those steps, but it would take a coupe of days. It would be nice if you can share your project.
Stay tuned...