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

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

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