Consuming SharePoint web services with a Java client

Using CAML for CRUD methods on SharePoint Copy and Lists services

1 2 3 Page 2
Page 2 of 3
  1. Properties pertaining to the document such as the Title can be set by adding a FieldInformation instance to the FieldInformationCollection. I've demonstrated this in Listing 5, where the title is set to the source-file name.
  2. You can upload a document to multiple locations by adding more than one destination to the DestinationUrlCollection instance. The configured properties file specifies a second location to be used.
  3. When looping through the CopyResultCollection (which is populated after making the service call) status codes and messages are returned for each destination URL that you've supplied. You can then check the result for each destination specified.
  4. If the file is versioned on SharePoint, uploading it with the uploadDocument() method will update the document along with its properties and increase the version number. Thus you can use the uploadDocument() method for updates as well as initial file uploads.

Listing 5. The uploadDocument() method

protected  void uploadDocument(CopySoap port, String sourceUrl)   throws Exception {

    File f = new File(sourceUrl);
    logger.debug("Uploading: " + f.getName());

    String url = getProperties().getProperty("copy.location") + f.getName();
    DestinationUrlCollection destinationUrlCollection = new DestinationUrlCollection();
    destinationUrlCollection.getString().add(url);
    if(getProperties().getProperty("copy.location2") != null){
         url = getProperties().getProperty("copy.location2") + f.getName();
         destinationUrlCollection.getString().add(url);
    }
        

    FieldInformation titleFieldInformation = new FieldInformation();
    titleFieldInformation.setDisplayName("Title");
    titleFieldInformation.setType(FieldType.TEXT);
    titleFieldInformation.setValue(f.getName());

    FieldInformationCollection fields = new FieldInformationCollection();
    fields.getFieldInformation().add(titleFieldInformation);

    CopyResultCollection results = new CopyResultCollection();
    Holder resultHolder = new Holder(results);
    Holder longHolder = new Holder(new Long(-1));
        
    //make the call to upload
    port.copyIntoItems(sourceUrl, destinationUrlCollection, fields, readAll(f), longHolder,resultHolder);
        
    //does not seem to change based on different CopyResults
    logger.debug("Long holder: " + longHolder.value);
        
    //do something meaningful here
    for (CopyResult copyResult : resultHolder.value.getCopyResult()) {              
        logger.debug("Destination: " + copyResult.getDestinationUrl());
        logger.debug("Error Message: " + copyResult.getErrorMessage());
        logger.debug("Error Code: " + copyResult.getErrorCode());
        if(copyResult.getErrorCode() != CopyErrorCode.SUCCESS)
            throw new Exception("Upload failed for: " + copyResult.getDestinationUrl() + " Message: " 
                    + copyResult.getErrorMessage() + " Code: " +   copyResult.getErrorCode() );
    }

}

Results

In the output shown in Listing 6, below, you can see that I have forced an error by trying to upload a document to two locations where the second location does not exist. This error helps me illustrate how to interrogate a CopyResult for each destination.

Listing 6. CopyResult output

2013-09-09 15:16:26,805 [main] DEBUG examples.SharePointBaseExample - Destination: https://abc.xyz.com/project/epms9615/Prod%20Support%20Folder/Daily%20Monitoring%20Status/AuditDeleteTesting/SharePointUploadDocumentExample.properties
2013-09-09 15:16:26,805 [main] DEBUG examples.SharePointBaseExample - Error Message: null
2013-09-09 15:16:26,806 [main] DEBUG examples.SharePointBaseExample - Error Code: SUCCESS
2013-09-09 15:16:26,806 [main] DEBUG examples.SharePointBaseExample - Destination: https://abc.xyz.com/project/epms9615/Prod%20Support%20Folder/Daily%20Monitoring%20Status/AuditDeleteTesting/Folder2/SharePointUploadDocumentExample.properties
2013-09-09 15:16:26,806 [main] DEBUG examples.SharePointBaseExample - Error Message: Object reference not set to an instance of an object.
2013-09-09 15:16:26,806 [main] DEBUG examples.SharePointBaseExample - Error Code: UNKNOWN
2013-09-09 15:16:26,808 [main] ERROR examples.SharePointUploadDocumentExample - Error caught in main: 
java.lang.Exception: Upload failed for: https://abc.xyz.com/project/epms9615/Prod%20Support%20Folder/Daily%20Monitoring%20Status/AuditDeleteTesting/Folder2/SharePointUploadDocumentExample.properties Message: Object reference not set to an instance of an object. Code: UNKNOWN
    at com.jw.sharepoint.examples.SharePointBaseExample.uploadDocument(SharePointBaseExample.java:178)
    at com.jw.sharepoint.examples.SharePointUploadDocumentExample.main(SharePointUploadDocumentExample.java:23)

Second demo: Deleting files on SharePoint using web services

Before I can show you how to delete a file on SharePoint I need to briefly demonstrate how to query SharePoint using the web services. The idea is that you will issue a query and retrieve a list of documents that are stored on SharePoint. Once you have the list you will be able to delete one or more of the documents found by the query operation. The code for this section is found in the SharePointDeleteListItemExample class. The main() method for this class is very similar to SharePointUploadDocumentExample's main(): it uploads a document to SharePoint, sends a query for the uploaded document, and ultimately deletes the document. I have added two additional lines to this class's main() method to achieve the query and delete functionality:

  1. ListsSoap ls = example.getListsSoap();
  2. example.executeQueryAndDelete(ls);

Querying a folder

In the previous example we used CopySoap to upload a document. For this example we'll use ListsSoap to query a folder and remove some of the documents it contains. The getListsSoap() method looks exactly like the getCopySoap() method, with the exception of the class it ultimately returns to the caller, so I won't detail it here.

The QueryOptions instance shown in Listing 7 is configured to have the getListItems() method recurse through all sub-folders on SharePoint for a given query. We achieve this by adding a static XML structure on the QueryOptions instance, by calling the getContent().add(Object o) method.

Listing 7. Query options


    TRUE
     
    TRUE 

The actual query the SharePoint web service will execute is defined by an org.w3c.dom.Node instance and is again a CAML XML block. Listing 8 shows an example of what the CAML query looks like at runtime, querying multiple document properties. In this example we query documents using three properties: Editor, FileRef, and Created_x0020_Date. For the Editor and FileRef properties we use a Contains operator. For the Created_x0020_Date property we use an Eq (equal) operator.

Listing 8. Query


    
    
        
             
                 
                Chandler
            
             
                 
                AuditDeleteTesting
                 
        
         
             
            2013-09-10
                       
              
    

In Listing 9 the static CAML XML structures are populated with data and then set on the Query and QueryOptions instances. Note the ListsSoap.getListItems(…) method parameters as well as the GetListItemsResult returned by the method.

Listing 9. Execute query

public void executeQueryAndDelete(ListsSoap ls) throws Exception {
    Date today = Calendar.getInstance().getTime();
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
    String formattedDate = simpleDateFormat.format(today);
    String queryFormatted = String.format(deleteListItemQuery,formattedDate);       
        
    GetListItems.QueryOptions msQueryOptions = new GetListItems.QueryOptions();
    GetListItems.Query msQuery = new GetListItems.Query();
    msQuery.getContent().add(createSharePointCAMLNode(queryFormatted));
    msQueryOptions.getContent().add(createSharePointCAMLNode(this.queryOptions));
    GetListItemsResponse.GetListItemsResult result = ls.getListItems(
            properties.getProperty("folder"), "", msQuery, null, "",
            msQueryOptions, "");

    writeResult(result.getContent().get(0), System.out);

    Element element = (Element) result.getContent().get(0);
    NodeList nl = element.getElementsByTagName("z:row");
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        String id = node.getAttributes().getNamedItem("ows_ID").getNodeValue();
        String fileRefRelativePath = node.getAttributes().getNamedItem("ows_FileRef").getNodeValue();
        logger.debug("id: " + id);
        logger.debug("fileRefRelativePath: " + fileRefRelativePath);
        String fileRef = properties.getProperty("delete.FileRef.base") + fileRefRelativePath.split("#")[1];
        logger.debug("fileRef: " + fileRef);
        deleteListItem(ls, properties.getProperty("folder"), id, fileRef);
    }
}

Listing 9 uses two different CAML structures, represented as org.w3c.dom.Node instances via the QueryOptions and Query instances. After those objects have the appropriate CAML structures set the getListItems method is invoked and in turn uses these structures to query the SharePoint site. The results are examined within the loop at the bottom of the function.

In Listing 10, notice that the GetListItemsResult instance contains result content in XML format, which can be cast to an Element instance for processing. In this example, we process all the nodes with the tag name of z:row. These nodes contain the metadata regarding the documents that were found as a result of executing the query defined by the CAML structure in Listing 8.

We use specific metadata from each row to aid in deleting specific documents returned from the query. Specifically, we use the ows_ID and ows_FileRef. Listing 10 is an abbreviated version of the XML returned from the query. It contains the namespace declarations as well as many of the attributes returned for the sake of brevity.

Listing 10. Query result XML

 

Deleting a list item from a folder

For the delete operation we'll use the ListsSoap.updateListItems(…) method shown in Listing 11.

Listing 11. Delete list item

public void deleteListItem(ListsSoap ls, String listName, String listId, String fileRef) throws Exception {
    String deleteFormatted = String.format(delete, listId, fileRef);        
    Updates u = new Updates();
    u.getContent().add(createSharePointCAMLNode(deleteFormatted));
    UpdateListItemsResult ret = ls.updateListItems(listName, u);
    writeResult(ret.getContent().get(0), System.out);
}

The function in Listing 11 also takes a CAML structure, which is shown in Listing 12. To successfully delete a file you must specify the ID which was returned in the query result XML as ows_ID for the file in question as well as the FileRef.

Listing 12. Delete XML structure


    
                1739
                        https://abc.xyz.com/project/epms9615/Prod Support Folder/Daily Monitoring Status/AuditDeleteTesting/SharePointDeleteListItemExample.properties
                  

The response returned isn't as friendly the CopyResult returned, in that it doesn't contain getters that can be invoked for error messages and status codes. But it does contain all the necessary data that you would interrogate in order to determine whether or not the call was successful on the server. A couple of XML structured responses are shown in Listing 13 and Listing 14.

Listing 13. Delete response (success)



    
        0x00000000
    

Listing 14. Delete response (failure)



    
        0x81020030
        Invalid file name

The file name you specified could not be used.  It may be the name of an existing file or directory, or you may not have permission to access the file.
        
    

Notice that when the FileRef filed information is missing (see Listing 14) the response returns an embedded z:row that contains all the information for the file in question based on the ID that we passed in Listing 12. You might wonder in that case why both the ID and FileRef parameters are required in the delete request. I suppose a CAML can only do so much.

In conclusion

In this Java tip I have showed you how to use CAML to execute standard CRUD methods on documents stored on SharePoint, based on my own experience of automating file uploads to SharePoint. I believe this tip should be very useful to Java developers because most companies that I have worked or consulted for utilized SharePoint for storing documents. In addition to using the demonstration classes, I hope that you will find other practical uses for the techniques described in this article. For instance, you could easily incorporate the process that I have demonstrated into build scripts, using it to write and save build results for easy access as well as the results of test automation. Any business process that has manual steps involving SharePoint or does not currently involve SharePoint, but does produce documents that must be saved, versioned, or easily accessible could be considered a candidate for the approach described in this tip.

Joe Chandler is a senior architecture specialist at Cigna in Denver, Colorado. He has more than 20 years of experience in design and development of both Java EE and .NET-based solutions with a strong focus in geospatial solutions due to his tenure at Autodesk. He holds a BS degree in Computer Information Systems from University of Colorado, Boulder, and an MS in Information Science from the University of Denver.
1 2 3 Page 2
Page 2 of 3