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

I Have Gone Dark

Maybe it's the Holidays, but my mood has gone pretty dark. That is, regarding the look and feel of my computer and Tridion CME, of course. What I did was to dim the lights on the operating system, so I installed Placebo themes for Windows 7 . I went for the Ashtray look -- great name :) My VM looks now like this: But, once you change the theme on Windows, you should 'match' the theme of your applications. Some skin easily, some not. The Office suite has an in-built scheme, which can be set to Black , but it doesn't actually dim the ribbon tool bars -- it looks quite weird. Yahoo Messenger is skinnable, but you can't change the big white panels where you actually 'chat'. Skype is not skinnable at all. For Chrome, there are plenty of grey themes. Now i'm using Pro Grey . But then I got into changing the theme of websites. While very few offer skinnable interfaces (as GMail does), I had to find a way to darken the websites... Enter Stylish -- a pl

REL Standard Tag Library

The RSTL is a library of REL tags providing standard functionality such as iterating collections, conditionals, imports, assignments, XML XSLT transformations, formatting dates, etc. RSTL distributable is available on my Google Code page under  REL Standard Tag Library . Always use the latest JAR . This post describes each RSTL tag in the library explaining its functionality, attributes and providing examples. For understanding the way expressions are evaluated, please read my post about the  Expression Language used by REL Standard Tag Library . <c:choose> / <c:when> / <c:otherwise> Syntax:     <c:choose>         <c:when test="expr1">             Do something         </c:when>         <c:when test="expr2">             Do something else         </c:when>         <c:otherwise>             Do something otherwise         </c:otherwise>     </c:choose> Att