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

Toolkit - Dynamic Content Queries

This post if part of a series about the  File System Toolkit  - a custom content delivery API for SDL Tridion. This post presents the Dynamic Content Query capability. The requirements for the Toolkit API are that it should be able to provide CustomMeta queries, pagination, and sorting -- all on the file system, without the use third party tools (database, search engines, indexers, etc). Therefore I had to implement a simple database engine and indexer -- which is described in more detail in post Writing My Own Database Engine . The querying logic does not make use of cache. This means the query logic is executed every time. When models are requested, the models are however retrieved using the ModelFactory and those are cached. Query Class This is the main class for dynamic content queries. It is the entry point into the execution logic of a query. The class takes as parameter a Criterion (presented below) which triggers the execution of query in all sub-criteria of a Criterio

A DD4T.net Implementation - Custom Binary Publisher

The default way to publish binaries in DD4T is implemented in class DD4T.Templates.Base.Utils.BinaryPublisher and uses method RenderedItem.AddBinary(Component) . This produces binaries that have their TCM URI as suffix in their filename. In my recent project, we had a requirement that binary file names should be clean (without the TCM URI suffix). Therefore, it was time to modify the way DD4T was publishing binaries. The method in charge with publishing binaries is called PublishItem and is defined in class BinaryPublisher . I therefore extended the BinaryPublisher and overrode method PublishItem. public class CustomBinaryPublisher : BinaryPublisher { private Template currentTemplate; private TcmUri structureGroupUri; In its simplest form, method PublishItem just takes the item and passes it to the AddBinary. In order to accomplish the requirement, we must specify a filename while publishing. This is the file name part of the binary path of Component.BinaryConten

Scaling Policies

This post is part of a bigger topic Autoscaling Publishers in AWS . In a previous post we talked about the Auto Scaling Groups , but we didn't go into details on the Scaling Policies. This is the purpose of this blog post. As defined earlier, the Scaling Policies define the rules according to which the group size is increased or decreased. These rules are based on instance metrics (e.g. CPU), CloudWatch custom metrics, or even CloudWatch alarms and their states and values. We defined a Scaling Policy with Steps, called 'increase_group_size', which is triggered first by the CloudWatch Alarm 'Publish_Alarm' defined earlier. Also depending on the size of the monitored CloudWatch custom metric 'Waiting for Publish', the Scaling Policy with Steps can add a difference number of instances to the group. The scaling policy sets the number of instances in group to 1 if there are between 1000 and 2000 items Waiting for Publish in the queue. It also sets the