Saturday, April 18, 2009

Calling Web Services from C++

Hi,
This post is about calling Web Services from C++. I faced this requirement when I had to access Alfresco from C++. So, I am writing it in the context of Alfresco.

I have been working on MS Visual C++ for long, never worked on Java and JavaScript. I had to use Alfresco as back-end and I had to provide client interface UI from my app (and not from browser) which is written in C++. Common tasks, which I required, include:
  • Authentication, if possible secure
  • Listing all the folders and files on Alfresco Server
  • Getting properties of files ( like Last Modified time )
  • Creating/deleting/moving/updating files and folders
  • Checkin/Checkout/Get Current Version/Revert to old version
  • Getting all the versions of a node
  • Able to get a particular version (other than the current one ) of a node on my client machine ( very important )
  • etc
I explored/googled and found that some of the tasks listed above can be done via WebDAV and Alfresco supports WebDAV interface. I had a WebDAV libraray, using which can do things like create (put) a file on the server. But my library doesn't support versioning and I doubt that Alfresco supports versioning using WebDAV ( Delta V Extension to be precise ).
Now, in order to accomplish all the tasks, what can I do ? As I have already stated, I had to stick to C++, can't switch to Java. I found that Alfresco also supports much of it's functionality via Web Services. I had never used Web Services before and had a very basic idea. I had so many questions.
  • Does all the requirements listed above can be achieved via Web Services ?
  • If yes, from where to start ?
  • what are all the Web methods exposed by Alfresco ?
  • how to create (put) a file on server using Web Service ?
  • Does Alfresco provides support to get a particular version (other than the current one ) of a node on client machine ?
  • Are there other means of accessing Alfresco ?

I came to know that Alfresco also supports FTP interface, and that it also supports Web Scripts. So, if I have HTTP Client available, I could call some methods using Web Scripts. I didn't explore that option, as I was more interested in calling Alfresco Web Services.

I did extensive googling and found that till Visual Studio 2005, there was an option of adding a Web reference to VC++ project. By doing so, Visual Studio automatically generates stubs for a web service given it's .wsdl file. This functionality is now discontinued ( for VC++ projects ) in VS 2008, which was installed on my system. On another system I had VS 2005 installed, and I tried my luck there with the .wsdl files supplied with Alfresco ( VersioningService.wsdl to be specific ), but it was not able to parse the file and was unable to generate stubcode. Googling revealed that stub code for C++ is generated using SPROXY.EXE, and this tool is very outdated, not conforming to newer standards. Microsoft do not recommend calling Web Service from native C++ any more, rather they suggest using WSDL.EXE to generate code in .Net compatible languages ( like managed C++, VB .Net , C# etc ). WSDL.EXE seems updated and was able to parse the alfresco .wsdl file without error. MS has made source code of SPROXY public. As I was interested in getting stub code for native C++, I downloaded the source code for SPROXY, compiled it and tried my luck, but all in vain ( I didn't have that much time to understand the source code, wsdl syntax,SOAP syntax and a lot to modify the source code ).Further googling helped in finding a toolkit gSOAP, which does the same thing as SPROXY.EXE does. I downloaded the same, compiled it ( needed to install flex and bison, faced so many issues, and it took time! ) to get the executables. I then gave gSOAP the input VersioningService.wsdl and it returned the code. The generated code looks too obscure to me, any ways I tried (just playing) to call checkOut function with input parameters. The error code which I got returned was saying Server Internal error. I couldn't get much info about the error. Also, I had no idea, how to specify parameters for the function checkOut, namely repositoryId and documentId, no idea whatsoever, how can I get the input values to supply. I then turned to C#. I added the web reference to VersioningService.wsdl and it generated C# code for the stub. I called the method checkOut() with empty input parameters and it threw an exception. I Examined the exception, the text was something like target service is null, and there was a big stack trace of server java code ( seemed like some settings need to done, don't know how and where) . The call to web service generated following message.

POST /alfresco/api HTTP/ 1.1

User-Agent: Mozilla/4.0 (compatible; MSIE 6 .0; MS Web Services Client Proto col 2.0.50727.83 2)

VsDebuggerCausalityData: [some lengthy string]

Content-Type: text/xml; charset=utf-8

SOAPAction : ""

Host: server-pc:8080

Content-Length: 363

Expect: 100-continue

Connection: Keep-Alive

xyz

abc

I had no idea whether the generated Soap was correct. I had so many doubts.

I kept on exploring the things and found some answers.

First thing is that the Alfresco APIs are exposed at http://server-pc:8080/alfresco/api/, for example http://server-pc:8080/alfresco/api/AuthenticationService ( name of the service can be found in.wsdl file ). I figured this out and tried with http://server-pc:8080/alfresco/api/VersioningService, only to find out that this service was not exposed. I think not all of .wsdl files have their corresponding services exposed. For repositryID and documentID thing, I would suggest not use VersioningService at all. Rather use AuthoringService for CheckOut and similar sort of things. You need to use AuthenticationService first to start a session.There is one more thing needs mention. All Web Services exposed by Alfresco seems secure, menas they will require security token in the SOAP header ( obtained from AuthenticationService ).

The moral of the story is, In C++, without proper support of any library, it can be a great pain working with Web Services.

If I get time, I will try to write a library in C++ to provide support for Web Services.

Thursday, April 16, 2009

Calling Alfresco Web Services from C#

Hi,
I am starting this topic to share my experience of calling Alfresco Web Services from .Net, the problems which you may face and the solutions.
I first started with scratch. I created a C# project and added a Web reference to AuthenticationService (authentication-service.wsdl) , and VersioningService (VersioningService.wsdl).
Then I wrote following lines of code


WindowsApplication1.AuthenticationService.AuthenticationService asvc = new WindowsApplication1.AuthenticationService.AuthenticationService();
AuthenticationResult ar = asvc.startSession("admin", "admin");

It worked fine. Examining ar showed the token returned by Alfresco.
I then wrote the following lines

VersioningService vs = new VersioningService();
string repID = ("xyz");
string docID = ("abc");
string docRefId = docID;
bool b = vs.checkOut(repID, ref docRefId);

I was expecting an exception to be generated for giving invalid parameters, but the exception generated was about target service is null. It seems VersioningService is not exposed at URL/alfresco/api/VersioningService.
I then tried to add reference to RepositoryService (repository-service.wsdl ). Visual Studio reported a problem:
The document was understood, but it could not be processed. The WSDL document contains links that could not be resolved.
The solution was to remove the nillable attributes in cml.xsd files. After doing that, it was able to add the reference. I then wrote the following lines

RepositoryService rs = new RepositoryService();
rs.Credentials = asvc.Credentials;
Store[] stores = rs.getStores();


The exception which got generated was about Unauthorization. It was simple to figure out the cause, I was not passing in the security token, it was hard to figure out how to pass that. I was stuck.
I then googled and found out that there is demo project called dotNet2 for calling Alfresco APIs from .Net. I downloaded the same and tried to build it. It turned out that there was a missing reference to Microsoft.Web.Services3 assembly. Solution was to install MS WSE 3. So well and good.
I tried building the solution and there was a problem:

Error 1 The best overloaded method match for 'WebServiceFactory.addSecurityHeader(Microsoft.Web.Services3.WebServicesClientProtocol)' has some invalid arguments
Error 2 Argument '1': cannot convert from 'Alfresco.RepositoryWebService.RepositoryService' to 'Microsoft.Web.Services3.WebServicesClientProtocol'

I tried google to find out the solution for this, and I got one but it didn't work. A close observation revealed that addSecurityHeader was expecting Microsoft.Web.Services3.WebServicesClientProtocol object the service objects which we were passing were derived from SoapHttpClientProtocol class. I found that Microsoft.Web.Services3.WebServicesClientProtocol itself derives from SoapHttpClientProtocol, so I just let the web services class derive from Microsoft.Web.Services3.WebServicesClientProtocol and it worked!!
Then I tried to call getStores() method of RepositoryWebService and I again encountered exception. There was some problem in class instantiation from the returned response. Close observation again revealed the problem. The StoreEnum of RepositoryWebService was defined like this

public enum StoreEnum {
///
workspace,
///
versionStore,
///
user,
///
search,
///
http,
///
system,
}

I extended this enum to look like this

public enum StoreEnum {
///
workspace,
///
versionStore,
///
user,
///
search,
///
http,
///
system,
archive,
avm,
}

And after doing this, the call succeeded
Now my task was to test checking out of a selected file and getting a previous version of a selected file. After many hit and trials, I got it working and here is the reference code (I added private AuthoringService authoringService; to Browse class)
This is the code for Checking Out a selected file:
private void CheckOut_Click(object sender, EventArgs e)
{
ListViewItem item = listViewBrowse.SelectedItems[0];
if (item != null)
{
ResultSetRowNode node = item.Tag as ResultSetRowNode;
if (node != null)
{
if (node.type.Contains("folder") == false)
{
// Create the reference for the node selected
Alfresco.AuthoringWebService.Store spacesStore2 = new Alfresco.AuthoringWebService.Store();
spacesStore2.scheme = Alfresco.AuthoringWebService.StoreEnum.workspace;
spacesStore2.address = "SpacesStore";
Alfresco.AuthoringWebService.Reference reference = new Alfresco.AuthoringWebService.Reference();
reference.store = spacesStore2;
reference.uuid = node.id;
// Lets try to check out
Alfresco.AuthoringWebService.Predicate predicate = new Alfresco.AuthoringWebService.Predicate();
predicate.Items = new Object[] { reference };
Alfresco.AuthoringWebService.ParentReference pr = new Alfresco.AuthoringWebService.ParentReference();
pr.store = spacesStore2; ;
pr.uuid = this.currentReference.uuid;
pr.associationType = Constants.ASSOC_CONTAINS;
pr.childName = Constants.createQNameString(Constants.NAMESPACE_CONTENT_MODEL,item.Text);
this.authoringService.checkout(predicate, pr);
}
else
{
// show message that a folder has been selected
}
}
}
}


And this is the code for getting the original version of a file
private void GetOriginalVersion_Click(object sender, EventArgs e)
{
Alfresco.RepositoryWebService.Store[] stores = this.repoService.getStores();
Alfresco.RepositoryWebService.Store vStore = stores[3]; // from data,I found out that this is for Versioned store
ListViewItem item = listViewBrowse.SelectedItems[0];
if (item != null)
{
ResultSetRowNode node = item.Tag as ResultSetRowNode;
if (node != null)
{
if (node.type.Contains("folder") == false)
{
// Create the reference for the node selected
Alfresco.AuthoringWebService.Store spacesStore2 = new Alfresco.AuthoringWebService.Store();
spacesStore2.scheme = Alfresco.AuthoringWebService.StoreEnum.workspace;
spacesStore2.address = "SpacesStore";
Alfresco.AuthoringWebService.Reference reference = new Alfresco.AuthoringWebService.Reference();
reference.store = spacesStore2;
reference.uuid = node.id;
VersionHistory VH = this.authoringService.getVersionHistory(reference);
int i = 0;
char[] temp = new char[1];
temp[0] = '0';
string versions = new string(temp);
Alfresco.AuthoringWebService.Version first;
foreach (Alfresco.AuthoringWebService.Version version inVH.versions)
{
if (i == 0)
first = version;
versions += version.label + (";") + version.id.uuid +(";");
}
{
// Create the reference for the node selected
Alfresco.ContentWebService.Store spacesStore3 = new Alfresco.ContentWebService.Store();
spacesStore3.scheme = Alfresco.ContentWebService.StoreEnum.versionStore;
spacesStore3.address = vStore.address;
Alfresco.ContentWebService.Reference reference1 = newAlfresco.ContentWebService.Reference();
reference1.store = spacesStore3;
reference1.uuid = VH.versions[VH.versions.GetUpperBound(0)].id.uuid;
// Lets try and get the content
Alfresco.ContentWebService.Predicate predicate = newAlfresco.ContentWebService.Predicate();
predicate.Items = new Object[] { reference1 };
Content[] contents = this.contentService.read(predicate,"{http://www.alfresco.org/model/content/1.0}content");
Content content = contents[0];
if (content.url != null && content.url.Length != 0)
{
string url = content.url + "?ticket=" + AuthenticationUtils.Ticket;
webBrowser.Url = new Uri(url);
}
}
}
else
{
// show message that a folder has been selected
}
}
}
}

Here is the link for the Holy Grail dotNet2 Demo project
http://forge.alfresco.com/frs/download.php/80/alfresco-dotNet-0.2Beta.zip

And here is the the project which will allow you to do a Check Out and Get Latest version. it is an extended version of dotNet2 Demo project. You should be able to head start with it. Change the Alfresco Server location as per your requirement.

http://cid-f49e53911f11f3b1.skydrive.live.com/self.aspx/.Public/Technical/Alfresco/alfresco-dotNet-0.2Beta-Modified.zip

Thanks,