Skip to main content

A Core Service Java Client

I have seen many times the creation of a .NET client for SDL Tridion's Core Service, but not once a Java client. In .NET / Visual Studio is extremely easy to create the proxy classes based on the service's WSDL. So how hard can it be in Java?

The entire code in this example is available in my Google Code project.

Which Tool?

Doing a bit of research on the multiple ways of creating Java proxy classes for web services left me quite confused as to which framework / utility to use. They are quite a few (AXIS, JAXB, Apache CXF, XFire, NetBeans plugin, IDEA plugin, Eclipse plugin, etc). They seemed to be cumbersome to use, too specific, limited, quirky, buggy, etc. What I wanted was a simple generic too that worked. Enter wsimport.exe...

Generating the Proxies

Note: Check out the update post available here http://yatb.mitza.net/2012/12/java-core-service-trouble-with-datetime.html

Wsimport.exe is part of the JDK (mine is located in C:\Progrm Files\Java\jdk1.6.0_33\bin), and in its simplest command-line syntax, it can be called like this:

wsimport.exe http://t2011sp1hr1.playground/webservices/CoreService2011.svc?wsdl

This command will load the WSDL and it will generate the Java proxy source classes. For me, using it was a breeze. It generated all proxies. I imported them in Eclipse and created my very first test client. It all went fine and the client worked. Shocking! :-)

CoreService2011 service = new CoreService2011();
ICoreService client = service.getBasicHttp();

UserData currentUser = client.getCurrentUser().getValue();
System.out.println(String.format("'%s' %s",
        currentUser.getTitle().getValue(), currentUser.getId().getValue()));

There were a few quirks, however: all objects (trafficked with the service) were wrapped inside JAXBElement objects. For example, strings were inside JAXBElement<String>, DateTime inside JAXBElement<XMLGregorianCalendar>, etc, according to the JAXB type convertion. I definitely didn't like that!

It turned out I should use a custom JAXB binding to get rid of the wrapping (see generateElementProperty) and to specify my own mapping between DateTime and java.util.Date (see documentation)):

<jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <jxb:globalBindings generateElementProperty="false">
    <jxb:javaType name="java.util.Date" xmlType="xs:dateTime"/>
  </jxb:globalBindings>
</jxb:bindings>

I put the XML above in file custombindings.xml and generated the proxies using the following command line:

wsimport.exe -d c:\temp\gendir -s c:\temp\srcdir -b c:\temp\custombindings.xml -verbose http://t2011sp1hr1.playground/webservices/CoreService2011.svc?wsdl

The code below worked out-of-the-box (the entire Java client code is available on my Google code project). It uses the BasicHttp binding to read the current user, read a Component and create a new Folder:

CoreService2011 service = new CoreService2011();
ICoreService client = service.getBasicHttp();

UserData currentUser = client.getCurrentUser();
System.out.println(String.format("'%s' %s", currentUser.getTitle(), currentUser.getId()));

ReadOptions readOptions = new ReadOptions();
ComponentData component = (ComponentData) client.read("tcm:1-852", readOptions);
System.out.println(String.format("'%s' %s", component.getTitle(), component.getId()));

FolderData folder = (FolderData) client.getDefaultData(ItemType.FOLDER, "tcm:1-1-2");
folder = (FolderData) client.save(folder, readOptions);
System.out.println(String.format("'%s' %s", folder.getTitle(), folder.getId()));

Basic Authentication

By default, the Core Service will use NTLM authentication. In cases where you are running the client from another server (than the TCM), or from a server in a different network domain, you should be using Basic HTTP Authentication. The only problem is that the JAXB client does not work with Basic Authentication.

The solution is to provide your own java.net.Authenticator that would provide your username and password during the authentication challenge.

The following is the Authenticator I used:

package mitza.coreservice.client;

import java.net.Authenticator;
import java.net.PasswordAuthentication;

public class BasicHttpAuthenticator extends Authenticator {

    private String user;
    private String password;

    public BasicHttpAuthenticator(String user, String password) {
        this.user = user;
        this.password = password;
    }

    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication(user, password.toCharArray());
    }
}

The final client code using the Authenticator is below:

public class Test {

    private static final QName Q_NAME = new QName("http://www.sdltridion.com/ContentManager/CoreService",
            "CoreService2011");

    public static void main(String[] args) throws Exception {
        BasicHttpAuthenticator basicHttpAuthenticator = new BasicHttpAuthenticator(args[0], args[1]);
        Authenticator.setDefault(basicHttpAuthenticator);

        URL url = new URL("http://t2011sp1hr1.playground/webservices/CoreService2011.svc?wsdl");
        CoreService2011 service = new CoreService2011(url, Q_NAME);
        ICoreService client = service.getBasicHttp();

        UserData currentUser = client.getCurrentUser();
        System.out.println(String.format("'%s' %s", currentUser.getTitle(), currentUser.getId()));

        ReadOptions readOptions = new ReadOptions();
        ComponentData component = (ComponentData) client.read("tcm:1-852", readOptions);
        System.out.println(String.format("'%s' %s", component.getTitle(), component.getId()));

        FolderData folder = (FolderData) client.getDefaultData(ItemType.FOLDER, "tcm:1-1-2");
        folder = (FolderData) client.save(folder, readOptions);
        System.out.println(String.format("'%s' %s", folder.getTitle(), folder.getId()));
    }
}

Notice the URL parameter passed to the CoreService2011 constructor. This allows you to specify to which Core Service server to connect to.


Comments

Charles said…
Thanks for the excellent article.
I tried to follow, and apparently our installation is using 2010 version of the core service. The URL is something like this: http://myserver.com/webservices/CoreService.svc?wsdl

I generated all the proxy code and changed the code according below, and everything compiled OK. I am getting "Authentication failure" in the "getCurrentUser()" call, I am not sure why. (I am pretty sure I am using the right user and password)

private static final QName Q_NAME = new QName("http://www.sdltridion.com/ContentManager/CoreService", "CoreService");

BasicHttpAuthenticator basicHttpAuthenticator = new BasicHttpAuthenticator(user, passwd);
Authenticator.setDefault(basicHttpAuthenticator);

URL url = new URL("http://myserver.com/webservices/CoreService.svc?wsdl");
CoreService service = new CoreService(url, Q_NAME);
ICoreService2010 client = service.getBasicHttp2010();
UserData currentUser = client.getCurrentUser();

Unknown said…
I noticed that if you run the Java client on the Content Manager server itself, then it won't actually use the BasicHTTP user/pass, and will instead go in with NTLM authentication and it will go in with the current user. Make sure your current user (the one you are logged in with) is a valid Tridion user.

Additionally, check that your Core Service webservice (in IIS) accepts Basic Authentication (in my case it does not, yet it still works).

Also, try commenting out line Authenticator.setDefault... That will let the request go in as the 'current' logged in user. You will have to have the current user added in Tridion. See what you get.

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