Skip to main content

Yet Another Java Mediator?

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:
  • 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

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:
  • 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.

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!

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:
  • 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.


Comments

Nivlong said…
Wow. I've definitely seen requests for templating options with Java in my last few projects. Impressive work, Mihai!
Charles said…
Excellent post!
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.
Unknown said…
Charles, not yet. It is still work in progress, although nearing completion. I'm only working on this in my very limited free time, "just for fun" :).

Stay tuned...

Popular posts from this blog

Running sp_updatestats on AWS RDS database

Part of the maintenance tasks that I perform on a MSSQL Content Manager database is to run stored procedure sp_updatestats . exec sp_updatestats However, that is not supported on an AWS RDS instance. The error message below indicates that only the sa  account can perform this: Msg 15247 , Level 16 , State 1 , Procedure sp_updatestats, Line 15 [Batch Start Line 0 ] User does not have permission to perform this action. Instead there are several posts that suggest using UPDATE STATISTICS instead: https://dba.stackexchange.com/questions/145982/sp-updatestats-vs-update-statistics I stumbled upon the following post from 2008 (!!!), https://social.msdn.microsoft.com/Forums/sqlserver/en-US/186e3db0-fe37-4c31-b017-8e7c24d19697/spupdatestats-fails-to-run-with-permission-error-under-dbopriveleged-user , which describes a way to wrap the call to sp_updatestats and execute it under a different user: create procedure dbo.sp_updstats with execute as 'dbo' as

Content Delivery Monitoring in AWS with CloudWatch

This post describes a way of monitoring a Tridion 9 combined Deployer by sending the health checks into a custom metric in CloudWatch in AWS. The same approach can also be used for other Content Delivery services. Once the metric is available in CloudWatch, we can create alarms in case the service errors out or becomes unresponsive. The overall architecture is as follows: Content Delivery service sends heartbeat (or exposes HTTP endpoint) for monitoring Monitoring Agent checks heartbeat (or HTTP health check) regularly and stores health state AWS lambda function: runs regularly reads the health state from Monitoring Agent pushes custom metrics into CloudWatch I am running the Deployer ( installation docs ) and Monitoring Agent ( installation docs ) on a t2.medium EC2 instance running CentOS on which I also installed the Systems Manager Agent (SSM Agent) ( installation docs ). In my case I have a combined Deployer that I want to monitor. This consists of an Endpoint and a

Debugging a Tridion 2011 Event System

OK, so you wrote your Tridion Event System. Now it's time to debug it. I know this is a hypothetical situtation -- your code never needs any kind of debugging ;) but indulge me... Recently, Alvin Reyes ( @nivlong ) blogged about being difficult to know how exactly to debug a Tridion Event System. More exactly, the question was " What process do I attach to for debugging even system code? ". Unfortunately, there is no simple or generic answer for it. Different events are fired by different Tridion CM modules. These modules run as different programs (or services) or run inside other programs (e.g. IIS). This means that you will need to monitor (or debug) different processes, based on which events your code handles. So the usual suspects are: dllhost.exe (or dllhost3g.exe ) - running as the MTSUser is the SDL Tridion Content Manager COM+ application and it fires events on generic TOM objects (e.g. events based on Tridion.ContentManager.Extensibility.Events.CrudEven