CHS-WS Notes

Mar. 13, 2007

Though I've been collecting some material for sometime, today more or less marks the kickoff of developing a set of web service interfaces to the Sakai Content Hosting Service. Writing a web service using Axis seems pretty simple, according to Alan Berg. Write some code, plop it in a .jws file in the right place, and viola! a web service. He gives this example::

mport org.apache.axis.AxisFault;

public class Ping{
  public String ping()throws AxisFault { 
     return "Pong";     
  }
}

This is then added to the /webapps/sakai-axis directory and deployed.

Digging a bit further, it looks like web services must be enabled in sakai.properties:

In sakai/sakai.properties add:
webservices.allowlogin=true

This wasn't set in my properties, so added it.
I have also enabled the Tomcat manager page on my tomcat instance. It can be accessed using manager/manager. It shows sakai-axis running.

Tried to connect to *https://localhost:8080/sakai-axis/SakaiScript.jws*, but it just timed out. Strange, really. I would expect either XML or an error message. Not a time out. The books seem to indicate that this should work.

Interestingly, when I supply, *http://localhost:8080/sakai-axis/SakaiLogin.jws*, I get the following text:

There is a Web Service here

Click to see the WSDL

when I click on this link, I see the XML for the WSDL of this service:

<wsdl:definitions targetNamespace="http://localhost:8080/sakai-axis/SakaiLogin.jws">

    <!--
    WSDL created by Apache Axis version: 1.3
    Built on Oct 05, 2005 (05:23:37 EDT)
    -->

...

I suspect that I needed to be logged in first to view the SakaiScript.jws file.

So I have verified that the web services on my Sakai sandbox (2.3.0) are functioning correctly. Presumably, I could write a ping (or helloworld) service like Allan's example abover and add it. This would be a useful exercise at some point. Better would be to figure out a simple Java-based client that can access these services.

The Apache Axis web site has what looks to be a very simple client:

1   import org.apache.axis.client.Call;
2   import org.apache.axis.client.Service;
3   import javax.xml.namespace.QName;
4   
5   public class TestClient {
6     public static void main(String [] args) {
7       try {
8         String endpoint =
9             "http://ws.apache.org:5049/axis/services/echo";
10  
11        Service  service = new Service();
12        Call     call    = (Call) service.createCall();
13  
14        call.setTargetEndpointAddress( new java.net.URL(endpoint) );
15        call.setOperationName(new QName("http://soapinterop.org/", echoString"));
16  
17        String ret = (String) call.invoke( new Object[] { "Hello!" } );
18  
19        System.out.println("Sent 'Hello!', got '" + ret + "'");
20      } catch (Exception e) {
21        System.err.println(e.toString());
22      }
23    }
24  }

This looks like it could be the basis of something I can use with Sakai. The plan is to create a very simple client in Eclipse and run it against the Sakai environment running on "localhost:8080". This should allow me to test web services on my own laptop without worrying put network connectivity, etc.

Mar. 14, 2007

Created an Eclipse project for the web service client. Copied Axis example (above) into it, but I need to add the Axis JAR. I can't find it in Tomcat (though it should be there). Downloaded in the Axis 1.4 binary package from Apache.

The following URL works (from the install instructions): http://localhost:8080/sakai-axis/services/Version?method=getVersion
Also this one: http://localhost:8080/sakai-axis/EchoHeaders.jws?method=list

These are both services included with Axis, now buried in Sakai.

I found the Apache Axis (1.3) JARs. They are indeed buried in the sakai-axis WAR. However, I have installed Axis (1.4) locally and will use those JARs instead.

Well, the example compiles, but doesn't run:

  • Unable to find required classes (javax.activation.DataHandler and javax.mail.internet.MimeMultipart). Attachment support is disabled.
    java.net.ConnectException: Connection refused: connect

This may be a simple configuration problem if, in fact, attachments are disabled.
I seem to be missing the mail JAR, at the very least.
Downloaded Java Mail API 1.4.
Downloaded Web Services Development Package, v 2.0 – didn't seem to have activation, which is part of RMI?
Downloaded JavaBeans Activiation Framework v 1.1. – that seemed to help.

Running the app no longer indicates missing classes, however the connection is still refused:

java.net.ConnectException: Connection refused: connect

Tried the URL (http://ws.apache.org:5049/axis/services/echo) in a browser and connection was denied there, too. I think it no longer exists.

Changed code to:

	public static void main(String[] args) {
	    try {
	      String endpoint =
	          "http://localhost:8080/sakai-axis/EchoHeaders.jws?method=list";

	      Service  service = new Service();
	      Call     call    = (Call) service.createCall();

	      call.setTargetEndpointAddress( new java.net.URL(endpoint) );
	      call.setOperationName(new QName("http://localhost:8080/", "list"));

	      String ret = (String) call.invoke( new Object[] { "Hello!" } );

	      System.out.println("Sent 'Hello!', got '" + ret + "'");
	    } catch (Exception e) {
	      System.err.println(e.toString());
	    }
	}

Ran it and got a different error message: java.io.FileNotFoundException: /EchoHeaders.jws.
It's an improvement on some levels, but still more work to do.

Changed code to a service we KNOW is there:

	public static void main(String[] args) {
	    try {
	      String endpoint = "http://localhost:8080/sakai-axis/SakaiLogin.jws";
	      Service  service = new Service();
	      Call call = (Call) service.createCall();

	      call.setTargetEndpointAddress (new java.net.URL(endpoint) );
	      call.setOperationName(new QName("http://localhost:8080/", "login"));

	      String ret = (String) call.invoke( new Object[] { "admin", "admin" } );

	      System.out.println("Sent 'admin,admin!', got '" + ret + "'");
	    } catch (Exception e) {
	      System.err.println(e.toString());
	    }
	}

This actually runs!

Sent 'admin,admin!', got '7f8b838c-bb59-474a-80c4-ef84734f7524'

Mar. 15, 2007

Looking over the SakaiScript.jws files, I note a couple of things:

  1. All of the operations are methods against a single web service end point (SakaiScript).
  2. Most take a session Id.
  3. Virtually return a String (there are a few that return boolean) - no arrays or objects.
  4. Methods are focused on adding data to Sakai - setters, adders, etc. Almost no data access.

Request granularity will be an issue, I believe. While individual methods to get at Id, Name, Description, etc can be provided against an object, it makes far more sense to return some kind of record or property map (associative array?) that minimizes request overhead.

Sakai may also need some way to flag sites as being open for web service access. There is a way to indicate public access, and perhaps that may suffice. Some kind of filter or paging will be needed as the number of sites in a projection environment can be enormous. Hierarchical organization of sites would help out quite a bit here. If sites were well-grouped into a heirarchy, I could expose it and allow the services to walk down the tree. Site Hierarchy won't be in Sakai until 2.5 (at least!).

Started work on ContentHosting.jws.
However, before I get too far into this, it's probably a good idea to update to 2.3.1 (at least). Eventually, this will need to be updated against 2.4.0 (though I am not expecting a lot of changes in the CHS API).
Environment rebuilt and running.

Mar. 20, 2007

Ran into a problem when I re-built the envionrment using 2.3.1. Eventually figured out that I needed to manually copy xercesImpl-2.6.2 in to make it work. I don't know why Maven didn't catch this, but didn't.
Verified working against Login service.

Created a very simple HelloWorld service as follows:

	public String hello( String sessionid) throws AxisFault {
		Session session = establishSession(sessionid);
		String ret = null;

		try {
			ret = "Hello, Web Service World!";
		}
		catch (Exception e) {  
		 return e.getClass().getName() + " : " + e.getMessage();
		}
		return ret;
	}

Works, with output as:

Sent 'admin,admin!', got 'bde15fb3-04a3-466b-80e9-e5af1c114897'
Sent 'sessionId', got 'Hello, Web Service World!'

Re-coded client to give a bit more information:

Sent SakaiLogin.login(admin, admin), got 'd6ccdb55-3a84-4337-00cd-c55d6b145f8b'
Sent ContentHosting.hello(sessionId), got 'Hello, Web Service World!'

Jeff Kahn and I have been talking about how to provide access to CHS collections. The problem is that each site has a CHS root, and there could be thousands of sites on a Sakai installation. We have settled on the idea of only providing access to those site that the user currently has access to and bundling those CHS roots into a virtual root. That would allow VUE to drill down from that root to any site that the user is allowed. It also play nicely against having access to multiple Sakai installations at once.

As such, a virtual root is created that is handled specially by the ContentHosting web services. When you get resources for this root, it always gives back the set of site roots that the user has access to:

String getVirtualRoot (sessionId);

Created the service method. Changed access to the hello method to the getVirtualRoot() method. Tested, works. Output from client is:

Sent SakaiLogin.login(admin, admin), got '22044410-1b10-41e8-008e-c87b1104043c'
Sent ContentHosting.getVirtualRoot(sessionId), got 'Virtual-Root-Identifier'

Now things start to get interesting. Once the client has the virtual root, it can ask for the resources in that collection.

Started to write a method that will get resources:

String getAllResources(sessionId, collectionId);

Getting the list of sites that a user has access to is a bit tricky. Steve Githens has a method in SakaiScript called getSitesUserCanAccess() in which he merges all sites the user can ACCESS with those they can UPDATE. I just went for the update case. Client has this output on accessing the virtual root:

Sent SakaiLogin.login(admin, admin), got '000e48d5-5352-4530-80cf-bac8143dcebc'
Sent ContentHosting.getVirtualRoot(sessionId), got 'Virtual-Root-Identifier'
Sent ContentHosting.getAllResources(sessionId), got 'Site count is 4'

Steve's code also serves as an example of returning information in XML, which is probably the preferred approach, rather than a comma separated list.
I have coded it as a list of resource elements, each with an id and name.

Code is failing with an unused id exception:

Sent SakaiLogin.login(admin, admin), got 'edff09c1-cec5-44e4-004f-9d15443f7b4c'
Sent ContentHosting.getVirtualRoot(sessionId), got 'Virtual-Root-Identifier'
Sent ContentHosting.getAllResources(sessionId), got 'org.sakaiproject.exception.IdUnusedException : null'

After some tinkering with the code, it produced:

Sent SakaiLogin.login(admin, admin), got 'ab49b574-e76c-4d90-80a8-ac8094cfc8a2'
Sent ContentHosting.getVirtualRoot(sessionId), got 'Virtual-Root-Identifier'
Sent ContentHosting.getAllResources(sessionId,virtualRootId), got '<?xml version="1.0" encoding="UTF-8"?>
<list>
    <resource>
        <id>/group/ContentTest/</id>
        <name>Content Test</name>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/</id>
        <name>Gallery Test</name>
    </resource>
    <resource>
        <id>/group/PortfolioAdmin/</id>
        <name>Portfolio Admin</name>
    </resource>
</list>

Likely it's my personal workspace that didn't have a CHS root. I caught the IdUnusedException and just skipped that entry. Those id's look a bit suspicious, so we will have to see if they are valid.

Mar. 21, 2007

Next steps are to expand getAllResources() to include regular collections. Also, I am thinking of renaming it to getResources() since we may want to have a getAllResources() that recurses down through collections. Very efficient for a web call, if tree is not large. The RESOURCE element in the XML should be expaned to include a TYPE element: either collection or resource. A check for attachments might be useful as well.

Create a getInfo() method that gets lots of information about a collection or resource.
Create a getData() method that gets the data associated with a resource.

Finally, create a CHSExplorer app that allows drill down, simple content view, etc.

Mar. 22, 2007

Changed getAllResources() to getResources().
Added resource types to entry information. Output looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<list>
    <resource>
        <id>/group/ContentTest/</id>
        <name>Content Test</name>
        <type>collection</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/</id>
        <name>Gallery Test</name>
        <type>collection</type>
    </resource>
    <resource>
        <id>/group/PortfolioAdmin/</id>
        <name>Portfolio Admin</name>
        <type>collection</type>
    </resource>
</list>

I'm going to extract the code that creates an resource information XML block. That way I can re-use in other parts of the web service code, such as getInfo().
Code has been extracted into getResourceBlock(ContentEntity, Document). Modified code in getResources() to use getResourceBlock(). Works correctly.

Mar. 23, 2007

Broke out virtual root test into it's own static method. Created a test static method that triest to get resources from the Gallery Test site using a hard coded root id string.

Expanded the code in getResources() to recognize the virtual root id and special case it, handle arbitrary collections, and handle the case where a resource id is submited. Tested both virtual root and collection id cases. Both work fine. In the case of a resource id pased, it is the single entry in a list.

Mar. 26, 2007

9:00am to 10:30am - NTB

Moved some code around in preparation to write the getInfo() service.

Unfortunately, some aspects of Sakai requires that it be on-line. In particular, the OSP code seemingly wants to validate some XML against a schema or DTD that lives on java.sun.com. Since I'm not connected and since this happens in a context load, Sakai destroys all loaded components, effectively bringing down Sakai.

Is there a requirement that Sakai be on-line (ie, connected to the internet) to run at all?

The relevant errors are:

INFO: registering tools from resource: /tools/osp.sample.jsf.widgets.xml (2007-03-26 09:44:07,812 main_org.sakaiproject.util.ToolListener)
ERROR: Context initialization failed (2007-03-26 09:44:07,843 main_org.springframework.web.context.ContextLoader)
org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from ServletContext resource [/WEB-INF/faces-config.xml]; nested exception is java.net.UnknownHostException: java.sun.com
java.net.UnknownHostException: java.sun.com

ERROR: Exception sending context initialized event to listener instance of class org.sakaiproject.util.ContextLoaderListener (2007-03-26 09:44:07,843 main_org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/osp-jsf-example])
org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from ServletContext resource [/WEB-INF/faces-config.xml]; nested exception is java.net.UnknownHostException: java.sun.com
java.net.UnknownHostException: java.sun.com

Mar 26, 2007 9:44:07 AM org.apache.catalina.core.StandardContext start
SEVERE: Error listenerStart
Mar 26, 2007 9:44:07 AM org.apache.catalina.core.StandardContext start
SEVERE: Context [/osp-jsf-example] startup failed due to previous errors
INFO: destroy() (2007-03-26 09:44:07,843 main_org.sakaiproject.archive.impl.ArchiveService2Impl)

....

The problem is that someone is attempting to validate JSF code against the schema definitions. It's only an example, but once it fails, it brings down all of Sakai, since the JSF component has failed to load.

Wrote the getInfo() service. Needs to be tested, but should basically work. Updated the markup for CHS Design page. Substanial changes.

This brings me to get and put data. Some research is needed. It seems quite likely that AXIS handles a file transfer case, so some searching on the apache site and the AXIS documentation should reveal it. If file transfer is simple (hope! hope!), I can quickly wrap up the CHS web services.

More client work should be done. First, an object class should be created to extract ContentEntity information into an object that can be used. Jeff K. will need something like this. Secondly, an interactive CHS explorer client will be a good test of the web services. It can (and should) be added to contrib as an example of how to use the CHS web services. I don't think it needs to be included in the mainline, especially since it would introduce a dependency on CHS from the webservices module. This isn't needed to make the web service work, so why bother increasing brittleness (unlike the OSP guys).

File Transfer

Reading through some of the AXIS documentation, I came across the transport example This seems to be an example of how to get and send files via a web service. Perfect for what I need to do with CHS.

FileTest is a simple client that takes arguments to it's main() method. Presumably the first of these is a file name, but username and password must also be provided. A Transport is set for the web service call which in this case is a simple extension of the built-in transport to handle file transport. It doesn't seem to do much beyond provided the name "FileTransport".

The call is specified by a WSDD (web service deployment descriptor). A pivot for FileTransport is specified as "pivot=\"java:samples.transport.FileSender\", which somehow connects it to the server service called FileSender.

File transport seems to be built on top of TCP transport, a means of streaming using TCP/IP (ie, a socket).

There is an email address in the code: dug@us.ibm.com who might be able to provide more insight into what's going on here.

Here is an interesting excerpt from the Axis User's Guide:

Axis supports scoping service objects (the actual Java objects which implement your methods) three ways. "Request" scope, the default, will create a new object each time a SOAP request comes in for your service. "Application" scope will create a singleton shared object to service all requests. "Session" scope will create a new object for each session-enabled client who accesses your service. To specify the scope option, you add a <parameter> to your service like this (where "value" is request, session, or application):

Axis supports the same context scopes that RSF does. Not a coincidence, I'm sure.

Check to see if AdminClient is included in the Sakai web services distribution.

The user guide uses an example that tracks web service use by logging them out to a file. This could be useful not only for debugging, but for security analysis purposes. It would make a nice (optional) feature of Sakai web services, provided it could be slipped in on top of the service definitions.

April 3, 2007

Started testing getResources() and getInfo() based on work done on 3/26. The getResources() service seems to work fine on any variation of collection, but is getting IdUnusedException if I supply a resource id. Put some println's in. I was expecting CHS to throw TypeException if I tried to get a collection with a resource id, but it seems to be throwing IdUnusedException instead. Added another catch block and re-tried to get id as a resource. That worked. This is the output from testGetResources() which tests three ids: virtual root, a colleciton, and a resource:

Sent SakaiLogin.login(admin, admin), got 'de99d3a6-101f-4630-80a8-1877e33d9c92'
Sent ContentHosting.getAllResources(sessionId,rootId), got '<?xml version="1.0" encoding="UTF-8"?>
<list>
    <resource>
        <id>/group/ContentTest/</id>
        <name>Content Test</name>
        <type>collection</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/</id>
        <name>Gallery Test</name>
        <type>collection</type>
    </resource>
    <resource>
        <id>/group/PortfolioAdmin/</id>
        <name>Portfolio Admin</name>
        <type>collection</type>
    </resource>
</list>
'
Sent ContentHosting.getAllResources(sessionId,collId), got '<?xml version="1.0" encoding="UTF-8"?>
<list>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0018.JPG</id>
        <name>Dunbarton Castle</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0076.JPG</id>
        <name>Cliffs of Mohr</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0153.JPG</id>
        <name>Ancient Dolman</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0303.JPG</id>
        <name>Blarney Tower</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0309.JPG</id>
        <name>Blarney Bailey</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0317.JPG</id>
        <name>Blarney Exterior</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0319.JPG</id>
        <name>Blarney Wall</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0320.JPG</id>
        <name>Blarney Interior</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0322.JPG</id>
        <name>Blarney Chamber</name>
        <type>resource</type>
    </resource>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0324.JPG</id>
        <name>Blarney Stair</name>
        <type>resource</type>
    </resource>
</list>
'
Sent ContentHosting.getAllResources(sessionId,resId), got '<?xml version="1.0" encoding="UTF-8"?>
<list>
    <resource>
        <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0018.JPG</id>
        <name>Dunbarton Castle</name>
        <type>resource</type>
    </resource>
</list>
'

Created a client test for getInfo(). Works with the following output:

Sent SakaiLogin.login(admin, admin), got 'aaec408f-27dc-46de-00ae-dac74063c308'
Sent ContentHosting.getAllResources(sessionId,rootId), got '<?xml version="1.0" encoding="UTF-8"?>
<resource>
    <id>Virtual-Root-Identifier</id>
    <name>Federated Collections</name>
    <type>collection</type>
</resource>
'
Sent ContentHosting.getAllResources(sessionId,collId), got '<?xml version="1.0" encoding="UTF-8"?>
<resource>
    <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/</id>
    <name>Gallery Tool Collections</name>
    <type>collection</type>
</resource>
'
Sent ContentHosting.getAllResources(sessionId,resId), got '<?xml version="1.0" encoding="UTF-8"?>
<resource>
    <id>/group/38414906-cfcb-485f-8047-99779b1d62b2/Gallery-Tool-Collections/Irish Ruins/DSC_0018.JPG</id>
    <name>Dunbarton Castle</name>
    <type>resource</type>
</resource>
'

April 15, 2007

The TCP Transport code references the AxisServer. Presumably, there is an Axis server that is started up when Sakai starts. The web.xml file in the webservices module refers to an AxisServlet. There is also a SOAPMonitorService and AdminServlet that are commented out. The former allows SOAP messages to be logged (for debugging purposes). the AdminServer is an admistration application that comes with Axis. This is probably disabled for security reasons in Sakai.

According to the user guide, it is possible to deploy a web service by creating a WSDD document. This descriptor gives a lot more flexibility than JWS files.
Here is the example given in the user guide:

<deployment xmlns="http://xml.apache.org/axis/wsdd/"
            xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
 <service name="MyService" provider="java:RPC">
  <parameter name="className" value="samples.userguide.example3.MyService"/>
  <parameter name="allowedMethods" value="*"/>
 </service>
</deployment>

In this example, the provider is "java:RPC", the remote procedure call handler. Note that there is also a FileProvider, which might do what we need it to do.
Here is the descriptor for the TCP provider:

<deployment name="test" xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
 <handler name="TCPSender" type="java:samples.transport.tcp.TCPSender"/>
 <transport name="tcp" pivot="TCPSender"/>
</deployment>

According the the User Guide, the AdminClient is used to deploy a service based on a WSDD file. We don't want this to be a manual process, though. However, looking at the server-deploy.wsdd, it lookes like we can register the handers and transports there.

Thinking about it, a regular web service sends a SOAP message to a server, that responds by sending a return message, enclosing data. That data is encloded as strings, structures as XML, etc. Objects are serialized and encoded. In theory, a client could make a service request for a file. The web service would then open up a socket, and send information about that socket to the client. The client then startes reading from the socket until EOF, at which time it is closed. Perhaps this is what the TCP transport example does.

A look through both the FileTransport files and the TCPTransport files doesn't reveal any connection between the two of them, except that TCP is a sub-directory of transport. Why?

Well, a read over the Axis documentation doesn't say much about how low level web services should work. I see two approaches:

  1. write a new hello world web service that uses a WSDD file and see if it works.
  2. start a detailed analysis of the file transfer example.

Eventually, both are likely to be needed.

FileTransport

Let's have a look at the FileTransport files:

client_deploy.wsdd
deploy.wsdd
FileReader.java
FileSender.java
FileTest.java
FileTransfer.java
readme

The readme file indicates the following:

To run the encoding samples, you must do the following:
1.  Set up your CLASSPATH.
2.  Deploy the service.
3.  Run the sample.

1.  Set up your CLASSPATH.
    The CLASSPATH must contain:  an XML parser (ie., Xerces), JUnit
    (www.junit.org), all the jars in the lib directory, and the directory
    containing the samples subdirectory.

3.  Deploy the service.
    To deploy the service, run:
        java org.apache.axis.utils.Admin server deploy.wsdd 

4.  Run the sample.
    Finally, to run the sample, run:
	java samples.transport.FileTest XXX

Presuming that we could install the code on the server, FileTest is a client app that creates a call to a service that provides a (delayed) quote for a stock symbol. Why this might be an example of file transfer elludes me. The call returns a float value which is the price quote.
FileSender is specified as the transport in the WSDD string in FileTest.

Here is an interesting quote from FileSender, "Not thread-safe - just a dummy sample to show that we can indeed use something other than HTTP as the transport." So perhaps this is wild goose chase. Maybe this is a FILE TRANSPORT rather than FILE TRANSFER.

April 17, 2007

Had a look at WSContent.jws on the contrib site. I was hoping that they had already handled file transfer. It provides for getting sizes and creating a collection. The services include:

// private method to establish the thread from session id.
private Session establishSession(String id) throws AxisFault

// get the content hosting service.
private  ContentHostingService getContentHostingService()

// get a the collection id associated with a site context.
private String getCollectionId (String context) {

// get a sites content size
public long getSiteCollectionSize(String sessionid, String context)

// get all site content size
public String getAllSitesCollectionSize(String sessionid)

// Create a folder given id and name.
public String createFolder(String sessionid, String collectionId, String name)

// Create a top level folder in a site.
public String createTopFolder(String sessionId, String context, String name)		

// Create a content item.
public String createContentItem(String sessionid,String name, String collectionId,String contentMime, 
	String description, String type, boolean binary)

This last service is of interest, since it seems to handle upload of files encoded as Base64 in the content Mime.
A close read looks like this will do it for us. It just encodes the file using a Base64 encoder utility and sends it as another string. No need for fancy transports or whatever. In fact, there isn't much need to re-write the service.

Ian Boston indicates that there is a new base64 object in sakai.utils. I'll have to track that down and use it.

I'll need to upgrade to CR10 in order to pick up the new base64 object (though it's in the trunk as well). Following that, I'm going to merge the two CHS web service files into a single set of services. There's almost no overlap, so I don't think that will be a problem. I'll preserve what was there exactly so existing client apps written against it won't have trouble. It might be useful to write a version of of get and put data that uses XML to reprsent the metadata. That will give users more flexibility as to what can be included.

New enviroment compiles and starts. Moved ContentHosting.jws into webservices/axis and it works ok as well.

Started work merging services from WSContent.jws into ContentHosting.jws. We can change the name easily enough when it comes time to put it in the trunk.
Removed the hello test service. It's no longer needed.

Essentially, five services need to merge my services with the existing ones in WSContent.jws:

  • getSiteCollectionSize(String sessionid, String context) (tick)
  • getAllSitesCollectionSize(String sessionid) (tick)
  • createFolder(String sessionid, String collectionId, String name) (warning)
  • createTopFolder(String sessionId, String context, String name) (warning)
  • createContentItem(String sessionid, String name, String collectionId, String contentMime, String description, String type, boolean binary)

I'm going to add these one at a time and test them as a I go to make sure the work ok.

Added getSiteCollectionSize() and added testSiteSize() to client. Works, return value is:

Sent SakaiLogin.login(admin, admin), got '9f5eb792-46b9-4657-0092-68fb4986b340'
Sent ContentHosting.getSiteCollectionSize(sessionId,siteId), got size of '12555'

Added getAllSitesCollectionSite(). It returns one DOM element per site with information in attributes:

<Assignments>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="!admin" size="-1" title="Administration Workspace" type=""/>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="~admin" size="0" title="Administration Workspace" type=""/>
	<site createdBy="admin" createdTime="Feb 25, 2007 2:17 pm"  id="ContentTest" size="64" title="Content Test" type=""/>
	<site createdBy="admin" createdTime="Jan 29, 2007 3:18 pm"  id="38414906-cfcb-485f-8047-99779b1d62b2" size="12555" title="Gallery Test" type="project"/>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="!gateway" size="12727" title="Gateway" type=""/>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="!urlError" size="12727" title="Invalid URL" type=""/>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="mercury" size="-1" title="mercury site" type=""/>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="!user" size="12727" title="My Workspace" type=""/>
	<site createdBy="admin" createdTime="Apr 10, 2007 2:08 pm"  id="283d3b44-ec66-4eb7-8072-c2c64f6f8755" size="-1" title="No Resources" type="project"/>
	<site createdBy="admin" createdTime="Mar 20, 2007 11:35 am" id="PortfolioAdmin" size="107" title="Portfolio Admin" type="portfolioAdmin"/>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="!error" size="12727" title="Site Unavailable" type=""/>
	<site createdBy="admin" createdTime="Jan 29, 2007 12:37 pm" id="!worksite" size="12727" title="worksite" type=""/>
</Assignments>

This approach isn't quite the same as the one I've taken using the Virtual Root. Furthermore, all sites are represented including the gateway, and several site templates. I don't think that's highly useful, personally. Still, it is possible to filter on site types.

The next two services allow collections (folders) to be created. Personally, I think these should be called "addRootCollection" and "createCollection". If I hear who actually wrote this code, I can negotiate with him over name changes, etc.

I'm not going to write test scripts for these services, just check that they compile ok. Creating folders is a bit out of scope for VUE and I am assuming that whowever wrote this tested it. Tested and compile bugs fixed (missing object imports).

This brings us down to createContentItem(), which is the one most relevant to my work for VUE. It handles file upload into an existing content collection. The reverse will also be needed.

April 18, 2007

Looks like there is a Base64 encoder object included in Axis. Since it's obviously designed to work with web services, this is the one that should be used in writing such services. The package name is: org.apache.axis.encoding.Base64.

Some good news on the developer front. It looks like Sakai can run unconnected to the internet (working at Lowell General Hospital right now).
Lots of exceptons on tomcat shutdown, however. I should look into those at some point, since some of them may be the fault of my development configuration.

Added createContentItem(). Minor changes are needed to decode from Base64 using the Apache Axis object.
Some clean up done on the code as well. Minor things, variable name changes, etc.

April 19, 2007

Wrote test code in client app. Works. Files are added to a test directory. File names and ocntent are generated on the fly, but could also have been read in from a file. I should test a binary file, like an image, but it seems to work.

Wrote getContentData() service. Tested it in the client method that uploads a file by immediately downloading it and displaying it. Works.