I used to think that a Camel was something to smoke or ride in the desert, but that was before I opened my mouth at work one day and said, "Sure I can automatically save these documents up to SharePoint." SharePoint has been around for a long time, so I assumed that there must be a Java API or possibly some exposed web services that I could consume. Well, as it turns out I did get the job done, and as with many things it ended better than it started. But there were quite a few hurdles along the way, which I'd like to help other JavaWorld readers avoid.
In this I'll show you how to perform basic CRUD operations on a SharePoint document folder from a Java client. The demonstration will focus on some of the more popular methods that any SharePoint web service consumer is likely to invoke, which are found in Microsoft's Copy and Lists services. For the CRUD operations we'll use CAML (Collaborative Application Markup Language), an XML-based language utilized in many of the methods exposed by Copy
and Lists
. You'll learn how to construct valid CAML structures that are passed as method parameters or assigned to object properties, which are in turn passed as parameters to these services.
Hopefully, this tip will convince you that there are CAMLs that you can use to get business documents from point A to point B, without relying on four legs and a hump.
Demonstration code
My demonstration code is very simple: I've used no open source code other than for logging and my implementation has no dependencies on Java EE technology, so you can run the source code directly from Eclipse in a standard Java application.
Background
I work in a group that performs information management for large volumes of data that eventually is housed in various data-marts (service, export, report, and so on). Consumers, both internal and external to the company, consume the data in order to make business and personal decisions. Various kinds of monitoring take place in this environment, including automated audits and reports that are run against the data housed in data-marts. Audits ensure that data is in a consistent state, both across marts and within the mart where it resides. Audit reports are emailed to various people and then manually saved on SharePoint.
Because the engines used for monitoring have a pluggable output writer concept, it was natural to consider setting up a writer for SharePoint. We already were writing to the database, SMTP servers, and a file system, so this seemed like a logical next step and a way to avoid the manual process.
The trick, of course, was making it all work.
Getting started: Communicating with SharePoint
The sample application for this article demonstrates how to communicate with SharePoint from a Java client. I wrote the application using Eclipse 3.6.2 and Java 1.6.0_32. Figure 1 shows the two main packages contained within the sample application.
The first package, com.jw.sharepoint.examples
, contains all the custom code for the solution. It uses the code contained in the com.microsoft.sharepoint.webservices
package, which was code-generated.
Before diving into how the custom code is structured I'll explain how to generate the Microsoft package. First, recall that we'll be using two web services to invoke service calls: Copy
and Lists
. You can access these services on the SharePoint site that you are trying to communicate with in the following locations:
- https://server/site/_vti_bin/Lists.asmx
- https://server/site/_vti_bin/Copy.asmx
Generating the web services package
To generate the code for the web services package we'll use wsimport
, which is located in the bin
directory of your Java installation, assuming that you're using Java 1.6 or higher. If your SharePoint site is running over HTTPS you might have a problem running wsimport
when pointing it directly to your server via the above URLs, in which case you would receive an error like this one:
[ERROR] sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPath
BuilderException: unable to find valid certification path to requested target
The problem in this case is that your cacerts
file doesn't have the certificate from the site. An easy way to get around this is to use your browser to download the WSDL files locally. For the example listed below I have done just that and saved the WSDLs in c:\temp\
. Listing 1 and Listing 2 show the code snippets I used to generate the source code, along with the output. You can ignore the warning for each service.
Listing 1. Copy service code generation
C:\temp>"%JAVA_HOME%\bin\wsimport" -d . -p com.microsoft.schemas.sharepoint.soap -keep -extension -Xnocompile Copy.wsdl
parsing WSDL...
[WARNING] SOAP port "CopySoap12": uses a non-standard SOAP 1.2 binding.
line 229 of file:/C:/temp/Copy.wsdl
generating code...
Listing 2. Lists service code generation
C:\temp>"%JAVA_HOME%\bin\wsimport" -d . -p com.microsoft.schemas.sharepoint.soap -keep -extension -Xnocompile list.wsdl
parsing WSDL...
[WARNING] SOAP port "ListsSoap12": uses a non-standard SOAP 1.2 binding.
line 1511 of file:/C:/temp/list.wsdl
generating code...
Once you've generated the code it's ready to be incorporated into the solution and used. You can remove the –Xnocompile
option from the wsimport
command. This option would cause the class files to be generated along with the source, but for this exercise we'll just copy the generated source files into the solution and let Eclipse compile them as if we had authored the source code.
A note about security
In order to successfully execute the SharePoint services I had to deviate from my normal method of web service consumption, which most always involves the use of Axis2. I quickly found that Axis2 has issues with the NTML authorization. It's possible to overcome these errors by using JCIFS in conjunction with Axis2 (see Resources) but that seemed like overkill for something relatively easy. With the approach I'm demonstrating there are no security hurdles to overcome. If your SharePoint site is using HTTPS you will need to ensure that the cacerts
file is updated with the site’s certificate (see Resources for details).
Because the examples are meant to be executed as console applications in Eclipse, I pass the following VM argument in the run configuration:
-Djavax.net.ssl.trustStore=path to your updated cacerts file
Custom code
The custom code for this solution is located in the com.jw.sharepoint.examples
package in the article source code. It contains a custom class for each of the SharePoint functions we'll be testing:
- SharePointUploadDocumentExample demonstrates how to upload a document to SharePoint.
- SharePointDeleteListItemExample demonstrates how to delete a document from SharePoint using CAML to query a list and delete a list item.
- SharePointListExample demonstrates how to query a folder on SharePoint using CAML and then interpret the results.
Note that I won't explicitly discuss the final class, SharePointListExample
. The SharePointDeleteListItemExample
class contains functionality for querying, so SharePointListExample
is presented for you to study on your own.
About the custom classes
As shown in Figure 2, each of the custom classes follows the same pattern and extends the SharePointBaseExample
class, which provides basic SharePoint functionality, as well as utility functions for dealing with CAML and XML. The custom classes also use specific properties files that they load via an initialize()
function that is called in main
. The properties files have all the properties needed for communicating with SharePoint and any other data that is required at runtime for the class in question.
Figure 2. Class diagram for the custom code (click to enlarge)
Each of the properties files located in the demonstration code's Configuration
directory has the name of the class it supports with a .properties
extension. Most of the properties contained in these files should be self-explanatory. Table 1 briefly describes the additional properties contained in SharePointDeleteListItemExample.properties
.
Table 1. Additional properties of the SharePointDeleteListItemExample class
Property | Description | Example value |
---|---|---|
username | The username for authentication to the SharePoint site. This should be fully domain qualified if running on Linux or using a different ID than used for Windows authentication. | Domain\bigbird |
password | The password to the SharePoint site | sesame |
wsdl | URL to the Lists.asmx WSDL | https://abc.xyz.com/project/epms9615/_vti_bin/Lists.asmx?wsdl |
Endpoint | URL to the Lists.asmx | https://abc.xyz.com/project/epms9615/_vti_bin/Lists.asmx |
Folder | The name of the base folder to use. | Prod Support Folder |
copy.wsdl | URL to the Copy.asmx WSDL | https://abc.xyz.com/team/eds/_vti_bin/Copy.asmx?wsdl |
copy.endpoint | URL to the Copy.asmx | https://abc.xyz.com/team/eds/_vti_bin/Copy.asmx |
copy.location | The location to place files for upload | https://abc.xyz.com/project/epms9615/Prod%20Support%20Folder/ Daily%20Monitoring%20Status/AuditDeleteTesting/ |
copy.sourceFile | The local file to use for uploading | Configuration/SharePointDeleteListItemExample.properties |
delete.FileRef.base | The base URL to the SharePoint site, used in delete file requests. | https://abc.xyz.com/ |
Additional configuration files
Some additional configuration files are located in the Configuration
directory. These are simple XML snippets written in CAML. We'll use these files, described in Table 2, throughout the solution.
Table 2. Additional configuration files
CAML file | Description |
---|---|
Query.xml |
A CAML file containing the query that we'll use to list files from the SharePoint server. This file shows an example of a query that uses three fields with two different data types (Text and DateTime ), as well as two different operators (Contains and Eq ). |
QueryOptions.xml |
A static file that we'll use throughout the examples to tell a SharePoint service that we want it to search all subfolders of the current folder. |
Delete.xml |
A CAML file that we'll use to delete SharePoint files. Strings are substituted at runtime. |
DeleteListItemQuery.xml |
A CAML file that we'll use to perform a query of candidate files available for removal from SharePoint |
First demo: Uploading a file to SharePoint
Our first exercise will be uploading a file to SharePoint via the CopySoap
web service. For this we'll use some of the classes that we generated in Listing 1 and Listing 2 by executing wsimport
on the Copy.asmx
.
In the SharePointBaseExample class you'll notice a method named getCopySoap()
. We'll use this method to return a generated CopySoap
instance, which we'll then use to upload a file by calling the method uploadDocument(CopySoap port, String sourceUrl)
.
The getCopySoap()
method is shown in Listing 3.
Listing 3. getCopySoap()
protected CopySoap getCopySoap() throws Exception {
logger.info("Creating a CopySoap instance...");
Copy service = new Copy(new URL(getProperties().getProperty("copy.wsdl")),
new QName("http://schemas.microsoft.com/sharepoint/soap/", "Copy"));
CopySoap copySoap = service.getCopySoap();
BindingProvider bp = (BindingProvider) copySoap;
bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, getProperties().getProperty("username"));
bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, getProperties().getProperty("password"));
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, getProperties().getProperty("copy.endpoint"));
return copySoap;
}
A Copy
class is instantiated using a two-argument constructor, which takes the copy service's WSDL location along with the QName
instance to use. We get the CopySoap
instance we need from the Copy
instance. Once this is done we can cast it to a BindingProvider
, which performs the protocol binding and contains the associated context objects for request-and-response message processing. From the BindingProvider
we can then set the username, password, and endpoint information on the request-context Map
.
Listing 4 shows the main
method of class SharePointUploadDocumentExample
. This method is very simple; it uses getCopySoap()
and uploadDocument(CopySoap port, String sourceUrl)
to upload a document to SharePoint. The source file to be copied to SharePoint is defined in the SharePointUploadDocumentExample
associated properties file, which it passes to the uploadDocument(…)
method via the copy.sourceFile
property value.
Listing 4. Upload document main method
public static void main(String[] args) {
logger.debug("main...");
try {
SharePointUploadDocumentExample example = new SharePointUploadDocumentExample();
example.initialize();
CopySoap p = example.getCopySoap();
example.uploadDocument(p, properties.getProperty("copy.sourceFile"));
} catch (Exception ex) {
logger.error("Error caught in main: ",ex);
}
}
uploadDocument()
Next we'll call the uploadDocument()
method. There are a few things to know about this method: