Enterprise Content Management

Armedia Blog

Archive for the ‘SharePoint’ Category

Modifying SharePoint Document Content Types and Libraries Using the Client Model

September 22nd, 2011 by Tim Lisko

In a current eRoom to SharePoint migration project I wanted to preserve the “Date Created”, “Date Modified”, “Edited By”, and “Created By” fields in the eRoom documents. To do this I created a custom content type (in SharePoint 2010) based on the standard “Document” content type with four new fields to accept the migrated information. I also created a library template that uses this content type as well as the standard “Document” content type. I’ll explain why later in this article.

Once the documents are migrated I need to update the migrated list/library items. What I don’t want is to keep one set of “preserved” fields and another set of SharePoint fields. The SharePoint fields, as you probably guessed, set the author and editor as the individual doing the migration (or impersonated member) and the created and modified dates being the date of migration.

Updating list items is a pretty easy thing to do in SharePoint’s Client Object Model. The following code accomplishes this task.

foreach (SP.ListItem item in lstItems)
{
//skip items not assigned to the migratedDoc document type
if (item.ContentType.ToString() == migType.ToString())
{
row = listItems_dtbl.NewRow();
row["List"] = list.Title;
title = (!Convert.IsDBNull(item["Title"]) ? item["Title"].ToString() : "");
if (title == "" && item["FileLeafRef"].ToString() != "")
{   //set title if one does not exist
title = item["FileLeafRef"].ToString();
title = title.Replace(item["File_x0020_Type"].ToString(), "");
}

sEditor = (!Convert.IsDBNull(item["eEditor"]) ? item["eEditor"].ToString() : "");
sAuthor = (!Convert.IsDBNull(item["eAuthor"]) ? item["eAuthor"].ToString() : "");

try
{
editor = ctx.Web.EnsureUser(sEditor);
ctx.ExecuteQuery();
}
catch
{
editor = ctx.Web.EnsureUser("tlisko");
}
try
{
author = ctx.Web.EnsureUser(sAuthor);
ctx.ExecuteQuery();
}
catch
{
author = ctx.Web.EnsureUser("tlisko");
}
item["Editor"] = editor;
item["Author"] = author;
item["Modified"] = item["eModified"];
item["Created"] = item["eCreated"];

//remove values
item["eEditor"] = null;
item["eAuthor"] = null;
item["eCreated"] = null;
item["eModified"] = null;

item["ContentTypeId"] = docType.Id;
item.Update();
}
}
try
{
ctx.ExecuteQuery();
}

“ctx” is my ClientContext, though that is probably self-evident. You may also note that I only process items whose content type is my custom content type “migratedDoc” which is instantiated as “migType.” Only the migrated documents will have this content type.

eRoom documents do not have Titles, so I set a title based on the file name. You can leave Title blank – it isn’t a required field. Continuing through the code you will notice that I check that the names of the editor and author exist in the LDAP (ctx.Web.EnsureUser(sAuthor)). The update of the list/library item will fail if you try to use an editor or author that doesn’t exist.

I don’t really want to have a custom document library – the SharePoint default document library is just fine. So to get the result I want I need to reassign all the migrated items to the standard “Document” type. List items content types have a Name property. Unfortunately you can’t change it – it is read only. What you can change is the ContentTypeId and changing that value changes the content type of the list item. In the code above “docType” is local instance of the “Document” content type.

I said at the beginning of this article that I would explain why I wanted to keep the “Document” content type in addition to the creating a custom content type. The reason is that you can’t assign a list item to a content type if that content type isn’t in the list. That’s also why you can’t move a document from one SharePoint library to another unless its content type is in the target library as well.

Next I want to remove my custom content type from the list altogether. But to do that, I have to make sure that all those extra fields that are in the custom content type are set to null – which I do before changing the content type. The order of resetting these fields and reassigning the content type doesn’t matter. What does matter is that if these extra fields are not set to null, you will be unable to remove the content type from the list.

After processing all the list items I can now remove the custom content type from the list. Easy, two lines of code:

migType.DeleteObject();
ctx.ExecuteQuery();

But that still doesn’t remove the custom fields from the list. More code is needed.  Incorporating the delete of the custom content type you have this.

migType.DeleteObject();
list.Fields.GetByTitle("eCreated").DeleteObject();
list.Fields.GetByTitle("eModified").DeleteObject();
list.Fields.GetByTitle("eAuthor").DeleteObject();
list.Fields.GetByTitle("eEditor").DeleteObject();
ctx.ExecuteQuery();

And that’s it! The library looks like the default document library with only the “Document” content type as desired.

Implementing Multiple Filters in LINQ Query from Visual Studio

September 22nd, 2011 by Tim Lisko

A best practice in any application making data calls is to push processing to the server and limit the amount of data that has to come back to the application.

I recently worked on a windows application that uses the Microsoft SharePoint Client Object Model to manipulate lists and their elements. I needed to filter the lists for processing in the application to just the document libraries that were not hidden, contained at least one item, and were not the “Site Assets” or “Style Library.”

Cleary a situation where filtering is desired. I found it easy to find examples for generating a list with one filter. For example:

1
2
3
4
5
6
7
var web = clientContext.Web;
SP.ListCollection listcoll = web.Lists;

ctx.Load(listcoll,
lists => lists.Include(list => list.Title)
.Where(list => list.BaseTemplate == 101));
ctx.ExecuteQuery();

However, examples on implementing multiple filters such as I needed proved much more challenging. I finally found an example where the implementation used many “.Where” clauses which led to the following attempt.

1
2
3
4
5
6
7
8
...
ctx.Load(listcoll,
lists => lists.Include(list => list.Title)
.Where(list => list.BaseTemplate == 101)
.Where(list => list.ItemCount > 0)
.Where(list => list.Title != "Site Assets")
.Where(list => list.Title != "Style Library")
.Where(list => list.Hidden == false));

Unfortunately this would not even compile in Visual Studio 2010.

I decided to try implementing a more common syntax used in ‘if’ statements – using ‘&&’ to combine multiple conditions but within one .Where.

1
2
3
4
5
6
7
8
...
ctx.Load(listcoll,
lists => lists.Include(list => list.Title, list => list.ItemCount)
.Where(list => list.BaseTemplate == 101 &&
list.ItemCount > 0 &&
list.Title != "Site Assets" &&
list.Title != "Style Library" &&
list.Hidden == false));

YES – Success! Finally, listcoll finally had the libraries I needed.

Debugging SharePoint 2010 Enterprise Edition Upgrade

June 28th, 2011 by Tim Lisko

I’ve been doing my SharePoint development and testing using a locally installed instance of SharePoint. I originally installed my development environment following the instructions found in the MSDN library: ”Setting Up the Development Environment for SharePoint 2010 on Windows Vista, Windows 7, and Windows Server 2008” which is basically the foundation level version.

This version of SharePoint was fine for quite a while, but the day came that I needed to create and test features that are found at the Standard or Enterprise levels. The good news is that you can install SharePoint Enterprise on top of your current installation and preserve the work you have already done (well, almost as I will explain later).

Unfortunately this installation wasn’t terribly smooth. It might have gone easier if I had stopped the SharePoint services and IIS, but the installation does this so that probably would not make any difference.

SharePoint installation files come with a configuration xml file. The  MSDN article I referenced above instructs you to add a tag within that file to enable installation on a windows client. I did that but the installation failed with some error about missing Office parts. I forgot that, in my original installation, I edited my configuration file to look like the xml file example in the MSDN article. So, after uninstalling SharePoint and editing the  configuration file again the installation went fine. Even better, when I launched SharePoint it opened up my original site!

So far, so good. Now to check on the Content Organizer feature, which was what I was primarily interested in.

Horrors! That feature wasn’t there! I rebooted and to my relief the Content Organizer feature appeared. So, going to my site features admin page I click the feature to activate it. That action generated an error:


The Site scoped feature being activated has a dependency on hidden Site Collection scoped feature ‘FeatureDefinition/0c8a9a47-22a9-4798-82f1-00e62a96006e’ (ID: ’0c8a9a47-22a9-4798-82f1-00e62a96006e’). Hidden features cannot be auto-activated across scopes. There may be one or more visible Site Collection scoped features that auto-activate the dependent hidden feature.


What?! A file search came up with a Feature.xml file for the Document Routing Resources Feature. In that file there was a Hidden property set to “True.” After changing that to “False” (turns out the setting should be “True” which I corrected later) I tried the activation again. Got a little further – the error message clued me to enable the Enterprise Features in Document Collection. I went back through both “Manage Site Features” and “Site Collection Features” to make sure everything that might relevant was checked. Still not able to enable the Content Organizer feature.

OK, what to do next? In Central Admin (Update and Migration) I checked the “Enable Enterprise Features” link – the feature set was set to Enterprise, so that was OK. I noticed then the link for “Enable Features on Existing Sites.” Ahhh – that looked promising.  I checked the “enable” option and “OK.” The message back indicated success! Checking my site I found the Content Organizer was activated,  but then trying to add a content rule caused an error. Also when I deactivated the feature I could not reactivate it!

Looking at my Site Settings I noticed other anomalies: I saw “Navigation” rather than “Top Link Bar” under the “Look and Feel” section. I didn’t see “Term Store management” under “Site Administration.” So there was more wrong here than just the feature I was immediately interested in.

I next tried to create a new site based on the “Document Center” template knowing that Content Organizer would be one of the features. That failed completely with a message similar that I got when trying to enable the Content Organizer. I could create a Team Site, so SharePoint was not entirely broken.

One last thing I thought to try before completely uninstalling SharePoint. I deleted the Document Collection on my site. I then created a new Document Collection.

Finally – success! All the expected site features were there and they worked! I lost the work invested in my original site, but luckily it was more of a testbed so that was not a complete disaster for me.

So, removing the document collection for the root site fixed all the issues that appeared after updating my development SharePoint environment to the Enterprise Edition. It is an extreme action, though not as extreme as uninstalling and reinstalling SharePoint. I’m pretty sure that you can save your document collection and redeploy using some admin tools, but I’m not terribly familiar with them and  as I mentioned I wasn’t too concerned about losing the site. I had a couple other sites on the server that I deleted, so I don’t know if the enterprise features would have worked on them after removing the document collection on the root. That might be interesting to investigate.

Using SP Designer 2010 to Add Custom Buttons

June 21st, 2011 by Tim Lisko

Sharepoint Designer  2010 provides a great quick way to add simple actions to your SharePoint application.

I have a project that I wanted to add a couple buttons that would allow the user to navigate away from a “Drop-Off” library to the libraries where files are directed by my content organizer rules. So, without thought to SP Designer I opened my VS 2010 and commenced to coding. The great thing about building your buttons in VS is the flexibility you have – but being so flexible means you have to build a lot even if you only want a little!

In this instance I only wanted my buttons in one library. Using VS2010 you start with an elements.xml file. Unfortunately the elements file can only point to a “type,” not a specific library or list. So, even though I only wanted to see my buttons in my “Drop Off” document library, all the document libraries would also display the buttons. You can certainly get around that by omitting the type and buttons and code them in or setting visibility, but I haven’t done that myself so I was less interested in getting the coding right and figuring out where to insert the coding.

Enter SharePoint Designer 2010. It provides an easy nterface for creating custom actions that aren’t complex. You can create buttons that will navigate to a form, initiate a workflow, or navigate to a URL. Even better, it only applies to the document library or list where you are adding the custom action – exactly what I wanted!

You can certainly add an image to your button – this was something I overlooked initially and was driving me crazy that I “couldn’t” add a button image! Well, of course you can add an image. Just move that scrollbar down on the side of the “Create Custom Action” screen to get to the “Advanced custome action options” – duh!

So, a couple limitations right off:

  • You can’t create tabs, groups, or context groups with SP Designer so far as I can see
  • SP Designer placed my buttons in the “Manage” group of the “Documents” tab. There does not appear to be anyway to direct your buttons to a specific ribbon group or tab that you might prefer.

In this case the SP Designer provided all the functionality I needed and saved me time I would have spent coding, testing, and deploying!

The latest word on SharePoint 2010 and CMIS REST bindings

June 15th, 2011 by dmiller

A while back I described the problems I had checking files out of SharePoint 2010 using CMIS REST bindings (more specifically, using the excellent Apache Chemistry OpenCMIS client API for CMIS). At the time, it seemed odd to me Microsoft would release a CMIS stack missing this critical feature.

Since then, I learned the SP2010 CMIS stack’s REST bindings do provide a way to check files out; so the feature isn’t completely broken after all.  However, SP2010′s approach doesn’t seem compliant with the CMIS standard.  So, although it works and it is RESTful, it will never be supported by general-purpose CMIS tools such as OpenCMIS.

Below is a sample message sent by OpenCMIS to check out a document using the REST bindings.  This message is CMIS-compliant, and does not work with SP2010:

<?xml version='1.0' encoding='UTF-8'?>
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom'
xmlns:cmis='http://docs.oasis-open.org/ns/cmis/core/200908/'
xmlns:cmisra='http://docs.oasis-open.org/ns/cmis/restatom/200908/'>
<atom:id>urn:uuid:00000000-0000-0000-0000-00000000000</atom:id>
<atom:title></atom:title>
<atom:updated>2011-05-03T15:23:45Z</atom:updated>
<cmisra:object xmlns:ns3='http://docs.oasis-open.org/ns/cmis/messaging/200908/'>
<cmis:properties>
<cmis:propertyId propertyDefinitionId='cmis:objectId'>
<cmis:value>1-3584</cmis:value>
</cmis:propertyId>
</cmis:properties>
</cmisra:object>
</atom:entry>

Below is the message that should be sent to SP2010 to successfully check out a document.  This message is not CMIS-compliant (as far as I can tell, anyway), and does work with SP2010:

<?xml version='1.0' encoding='UTF-8'?>
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom'
xmlns:cmis='http://docs.oasis-open.org/ns/cmis/core/200908/'
xmlns:cmisra='http://docs.oasis-open.org/ns/cmis/restatom/200908/'>
<atom:id>urn:uuid:00000000-0000-0000-0000-00000000000</atom:id>
<atom:title></atom:title>
<atom:updated>2011-05-03T15:23:45Z</atom:updated>
<cmisra:object xmlns:ns3='http://docs.oasis-open.org/ns/cmis/messaging/200908/'>
<cmis:objectId>1-3584</cmis:objectId>
</cmisra:object>
</atom:entry>

The OpenCMIS request (shown first) includes a property list, with the CMIS object ID as the only property. The SP2010 request (shown second) does not include a property list, just a <cmis:objectId> tag.

That <cmis:objectId> tag is what makes the SP2010 request non-compliant.  The CMIS Core XML Schema defines no such tag; “cmis:objectId” appears only as an enum value for a property name (as shown in the OpenCMIS request).

Suppose you really wanted to use the REST bindings, what should you do? Well, you have to write a wrapper around OpenCMIS (or your other client CMIS stack of choice). The file checkout code in your wrapper bypasses your CMIS client stack, and issues a message as shown above, using whichever HTTP client software you are comfortable with. HTTP client software is pretty easy to work with, but don’t forget to parse the response and deal with the many different error codes that might be returned!

In reality this seems like too much work, especially since SP2010′s WSDL bindings seem to work just fine. So my advice is unchanged: Avoid the CMIS REST bindings if you plan to support SharePoint 2010 (or think you may support it in the future).  Stick with the WSDL bindings.

Adding and Removing SharePoint Features – Painful Lessons Learned

June 10th, 2011 by Tim Lisko

My SharePoint development environment is my laptop. In this environment I have developed various kinds of web parts and have been deploying them to my local instance of SharePoint. So of course, the day came when I wanted to deploy my web part to an actual server. (why else would you be doing development!)  

Many of the blogs I read say that you need to develop and deploy on the actual server. This strategy has never made sense to me. And, of course, the strategy wasnt where my confusion ended. There are any number of references for doing this. MSDN Library: Create Visual Web Parts for SharePoint 2010 for creating a web part. Technet – Deploy Solution, and Technet – Deploy Feature for deploying. Essentially the steps are to (using Visual Studio 2010)

  1. Create a web part
  2. Deploy
  3. Copy solution file (.wsp) to the SharePoint server
  4. Add the solution to the web site using the SharePoint Management Shell console.
  5. Deploy using either the SharePoint Management Shell or Central Administrator.

So I build and deploy my solution and copy to file to the server. You can find the solution file by opening the Solution Explorer in VS2010 and checking the “Project Folder” property of your project. For me this path is “C:\Users\tlisko\Documents\Visual Studio 2010\Projects\mywebpart\”.  Then open the “\bin\Release” folder where you should find one “.dll”, one “.wsp”, and one “.pdb” file of your solution.

I logged in to the SharePoint server as the administrator, then added and deployed the solution just fine. Opening my SharePoint site I inserted the new web part into a page. I decided that I wanted to make a change to it. No problem, just make the change in Visual Studio and repeat the deployment process.

Doesn’t work that way, at least not completely. I copied the file, but since the old wsp file was there I had to use a different file name. No big deal. Then I tried to execute the “update-spsolution” cmdlet in the SharePoint Management Shell. That seemed to run fine. When I opened the Central Administration web site the solution status indicated that it was updating(deploying). Funny thing, the status never changed! To me that meant something wasn’t working – perhaps had I waited longer it would have worked, but I don’t think so.

So, I cancelled the deployment and tried to retract. Once again, once status was set to “retracting” there was no completion. I then removed the new web part from the web site where it was deployed. Tried retracting the solution again, but no luck. Ok, now I’m getting serious! :-) Going back to the SP Management Shell I see that the the solution appears as deployed. So – I run the “uninstall” cmdlet but, of course, it says the solution is deployed. Run again with -AllWebApplications flag. Seems to work. Now I want to remove the solution. When I run that cmdlet I get an error about only machine administrators being allowed to create service job definitions!

Ok – now what. It made sense to me that the admin user I was logged in as needed to be owner of the content databases in SQL Server (though I wouldn’t have been able to deploy in the first place if that wasn’t the case). SQL Server permissions are fine. But, I observed that the SQL Agent wasn’t running. Not sure if that would help I started it anyway. What else can I do? Well there is a “force” flag in the “Remove” cmdlet. Yup, same results as before. Still get the error.

When all attempts fail, try rebooting…That made things worse  – SP wasn’t working anymore! (for reasons I do not wish to disclose on the grounds it will incriminate me!) I got the SP site working again, but naturally the problem of removing the solution remained.

Doing a web search I found a blog by JPPinto.com that addressed this very error! As unlikely as it seemed the cause of all this pain was due to the infamous User Account Control (UAC). Once I disabled that everything worked!

Just another day at the office…

CMIS, SharePoint 2010, and File Checkins

June 3rd, 2011 by dmiller

A while back I wrote about checking out a SharePoint 2010 file using CMIS.  Getting checkout working excited me so much I forgot to try checking the file back in!

Turns out there are a few tricks.

We use Apache Chemistry as our CMIS client.  Chemistry’s checkin method allows you to provide a file content stream, such that the act of checking the file in also updates the file contents:

checkin(boolean major, Map<String,?> properties, ContentStream contentStream, String checkinComment)

However, sending a non-null contentStream to this method makes it fail!  You will get the famous “object reference not set to an instance of an object” message.  You must send null for this parameter.  So how do you update the file contents?  Right before the checkin, you can call the setContentStream method, as illustrated in the below code snippet.

Also, in the properties parameter, you must provide a property for the object name.  This is also illustrated in the below snippet.  You may provide other properties if you want, but if you don’t provide the object name, the checkin will fail.

The JUnit test case below illustrates how to connect to SP2010 via WSDL, find an object by path, check out the object, update its content stream, and check it back in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package com.armedia.acm.ecm.service;

import java.io.InputStream;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.ObjectId;
import org.apache.chemistry.opencmis.client.api.Repository;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.client.api.SessionFactory;
import org.apache.chemistry.opencmis.client.bindings.CmisBindingFactory;
import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.enums.BindingType;
import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.BindingsObjectFactoryImpl;
import org.apache.chemistry.opencmis.commons.spi.BindingsObjectFactory;
import org.junit.Test;

/**
*
* @author millerd
*/

public class SharePointWSDLTest
{

@Test
public void connectViaWsdl() throws Exception
{
String cwd = System.getProperty("user.dir");
System.out.println("Working dir: " + cwd);
// Default factory implementation of client runtime.
SessionFactory sessionFactory = SessionFactoryImpl.newInstance();
Map parameter = new HashMap();

// User credentials.
parameter.put(SessionParameter.USER, "some_user");
parameter.put(SessionParameter.PASSWORD, "some_password");

// relative file-based URL works fine so long as you give it the
// right relative path... ie. you have to know what the current
// working directory is.
String wsdl = "file:target/classes/acmv2-sharepoint.wsdl";
// Connection settings.
parameter.put(SessionParameter.BINDING_TYPE, BindingType.WEBSERVICES.value());
parameter.put(SessionParameter.WEBSERVICES_ACL_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_DISCOVERY_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_MULTIFILING_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_NAVIGATION_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_OBJECT_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_POLICY_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_RELATIONSHIP_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_REPOSITORY_SERVICE, wsdl);
parameter.put(SessionParameter.WEBSERVICES_VERSIONING_SERVICE, wsdl);

parameter.put(SessionParameter.AUTHENTICATION_PROVIDER_CLASS,
CmisBindingFactory.STANDARD_AUTHENTICATION_PROVIDER);

// Create session.
Session session = null;

List repositories = sessionFactory.getRepositories(parameter);
for ( Repository rep : repositories )
{
// My test file is in the "Unapproved Files" repository
if ( "Unapproved Files".equals(rep.getName()) )
{
session = rep.createSession();
Document doc = (Document)
session.getObjectByPath("/2011/05/03/10/31/19/dave-sharepoint.wsdl");
System.out.println("Doc ID: " + ( doc == null ? " *NOT FOUND* " : doc.getId()));
if ( doc != null )
{
try
{
ObjectId co = doc.checkOut();
System.out.println("Checked out doc ID: " + co.getId());
}
catch (CmisBaseException cbe)
{
System.out.println("Exception checking out; must be " +
"checked out already: " + cbe.getMessage());
}

// create a new content stream to update the file contents
InputStream is = Thread.currentThread().
getContextClassLoader().getResourceAsStream(
"dave-sharepoint.wsdl");

boolean majorVersion = true;
Map props = new HashMap();
// Object name MUST b eprovided
props.put(PropertyIds.NAME, "dave-sharepoint");

BindingsObjectFactory bof = new BindingsObjectFactoryImpl();

ContentStream csNew = bof.createContentStream(
"dave-sharepoint.wsdl",
BigInteger.valueOf(is.available()),
"text/plain",
is);

// OPTIONAL: update the file content stream
doc.setContentStream(csNew, true);
// NOTE: DO NOT send a content stream in the checkIn call!
// the call will fail
ObjectId ci = doc.checkIn(
majorVersion,
props,
null,
"test checkin");
System.out.println("Object ID after checkin: " + ci.getId());
}
}
}

}
}

HOW TO: Retrieve and Display CMIS data in SharePoint using REST Atom Service

May 31st, 2011 by Tim Lisko

In a previous blog I discussed “how to” enable the Content Management Interoperability Service (CMIS) in SharePoint 2010. In that blog I also reviewed the SharePoint built in Web Part for getting data from a CMIS enabled site using WSDL.

In this blog I will show how you can access CMIS data in SharePoint using the REST Atom Service. I borrowed primarily from Jaspers’ Weblog for how to do this and from the msdn guidance for implementing CMIS. I ran into one problem along the way which I will discuss at the end.

The first step in getting data from a REST Atom Service is to create a data provider control in SharePoint.

Creating a REST Atom Service data provider control in SharePoint 2010

1) Open SharePoint Designer and open the site you wish to display the remote data
2) Select “Data Sources” from the list of Site Objects in the navigation panel on the left part of the screen.
3) Click the “REST Service Connection” button from the “Data Sources” ribbon.

A dialog box for entering Data Source Properties will open. Enter the properties as follows.

General Tab
Enter any name, description, and keywords that you wish

Source Tab
• Choose a HTTP method and data command.
• Enter connection URL.

MSDN URL Format
http://Site URL/_vti_bin/cmis/rest/[Library or List GUID]?command
Example
http://armws2008sp-01:2397/_vti_bin/cmis/rest/EF9A4CB3-BEF0-44B3-975A-611601D47425?getRepositoryInformation

Jasper’s Weblog URL Format:

http://web.site.com/sub/site/_vti_bin/ListData.svc/ListName
Example
http://armws2008sp-01:2397/_vti_bin/ListData.svc/SharedDocuments

Login Tab
• Select “Save this username and password in the connection”
• Enter the username and password.

4) Click “OK” to save.

All examples in step 3 work fine in the Firefox REST client extension. You would act on data in lists and libraries following the syntax from the MSDN example. In this case you have to have the GUID of the list or library. You get a list of lists and libraries, list items, and documents and their GUIDs following the example in Jasper’s web log.

Displaying REST provider data in a DataView

The following provides instructions for implementing a SharePoint DataView using a REST provider. This information comes primarily from Jaspers’ Web log.

1) Open the SharePoint site where REST/CMIS data is to be displayed in SharePoint Designer
2) Open the page where you wish to insert the DataView
3) Select the “Insert” ribbon.
4) Select the “Data View” button from the ribbon and select the REST data source.

You can modify the presentation of the data view and which fields are shown while looking at the web part’s properties while still in SharePoint Designer.

REST Data Issue

I ran into a problem trying to get the CMIS data to display in the DataView control. I used the REST client extension in Firefox to investigate the problem. The response I would get started with

Status Code: 405 Method Not Allowed

For reasons I don’t recall – possibly inspecting events in the windows event viewer of the server I was targeting in my URL – my next step was to test the ListData.svc, which you can do by opening a browser to the ListData.svc URL:

http://Web site or Localhost/_vti_bin/listdata.svc/
http://armws2008sp-01:2397/_vti_bin/ListData.svc/, for example

The error I got back was

“Could not load type ‘System.Data.Services.Providers.IDataServiceUpdateProvider’ from assembly ‘System.Data.Services, Version=3.5.0.0…’”

A google search brought me to “Michael’s coding thoughts” blog. His similar issue was solved by installing an update to ADO.NET Data Services, which has in it the REST services.

In retrospect I should have checked if I had the updated dll version before running the update installation. Looking at the GAC it appeared to be version 3.5.0 which didn’t change after running the update. I finally realized I had to select the file and view its properties to verify the actual version In any event, that did not make the problem go away for me.

Further searching led me to the” XML Journal” blog which identified the absence of ADO.NET Data Services 1.5 CTP2 as the cause of this issue. I installed this software, though for me, as for the author, it did not appear to fix the problem. Following his example I rebooted the server and the problem went away – just as it did for the blog author.

With any luck this information will help you if you are trying to implement CMIS in SharePoint 2010!

 

Q: Why should I test my CMIS application against multiple repositories?

May 26th, 2011 by dmiller

A: To uncover my hidden assumptions!

Let me tell you a story about SharePoint, Alfresco, design choices, and downloading files.

My Java-based application uses SharePoint 2010 to store files.  CMIS is the obvious tool for my app to talk to SP 2010, and in fact, CMIS and SP2010 work just fine together, as you may have gathered from my colleague Tim Lisko’s last few articles.

So it came time for me to write the download feature, thus allowing users to download the files stored in SP2010 from my application.  Chemistry makes it very easy to retrieve a file object by ID:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public InputStream getFileContentStream(Session session, String objectId)
{
try
{
Document file = (Document) session.getObject(objectId);
ContentStream csFile = file.getContentStream();
InputStream retval = csFile.getStream();
return retval;
}
catch (CmisBaseException cbe)
{
throw new AcmRepositoryException("Could not get file contents",
cbe);
}
}

So I said to myself: OK, I’ll just construct a URL including the CMIS object ID, then I’ll pass that object ID straight through to my Java method.

All you guys that have used CMIS against both Alfresco and SharePoint (hi Colin!) know what’s coming next.

SP2010′s object ID looks like this: 42-512.  So my URL’s looked like this:

http://<host>/<context>/docs/download/2316/42-512

No problems here!  A nice valid URL, and my download code worked great!

So yesterday, I had an idea (actually, my boss had the idea). Let’s stand up the app against Alfresco!

Alfresco’s object IDs turn out to look like this:

workspace://SpacesStore/ecd60ff9-adbb-45fb-824d-9bc93aa61b0f

Which turned my download URL into this grim-looking thing:

http://<host>/<context>/docs/download/2316/workspace://SpacesStore/ecd60ff9-adbb-45fb-824d-9bc93aa61b0f

I’ll leave what happened when I clicked that link to your imagination (hint: it wasn’t pretty!).

Since I wanted to be up and running on Alfresco pretty quickly, I applied a short-term fix: I replace the colons and slashes in the object ID with strings that should not be part of any real object ID, and my download controller reverses the process.  Now my URLs look like this:

http://<host>/<context>/docs/download/2316/workspace-c!–s!–s!-SpacesStore-s!-ecd60ff9-adbb-45fb-824d-9bc93aa61b0f

The good: the download code works again.  The bad: I shouldn’t have to encode and decode my own download URL this way!  The ugly: as you can see, the aesthetics of the above URL leave a lot to be desired.

The long-term fix (by “long-term” I mean “by Monday”) is to not place the object ID in the URL in the first place, and use an opaque sequence-driven numeric database ID instead.  Then all my URLs will look like this, no matter what repository is in use at the moment:

http://<host>/<context>/docs/download/2316/42

Why didn’t I do this in the first place?  Well, I certainly should have.  For the most part I would never use a natural key such as the CMIS object ID in this situation.  I’m pretty sure I was trying to avoid a database lookup.  In the end I needed to do a database lookup anyway, but it never occurred to me to rethink my earlier design choices.

SharePoint 2010 – REST Atom Service and JAVA, part 2

May 26th, 2011 by Tim Lisko

This is the second of two blogs where I demonstrate how to use the openCMIS library from a JAVA client to connect to a CMIS enabled SharePoint site through the REST Atom service of SharePoint.

My previous blog “SharePoint 2010 – REST Atom Service.” provided information on how to connect to a SharePoint site and examined some of the SharePoint site’s capabilities. In this blog I will show how to use the openCMIS library and REST Atom Service to: 1) Access and Create Folders; 2) Iterate over SharePoint folders; and 3) Add documents to a SharePoint folder. This blog builds on the previous one, so reference it if you aren’t able to follow the code in this one.

Accessing and Creating SharePoint Folders

The following exmple will show you how to get and create folder objects. An additional class library is imported for accessing folders and another for getting a CMIS object by path.

 
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.CmisObject;

Folder folder = session.getRootFolder();
String folderprops=twolines + “ROOT FOLDER:” + newline +
tab + “ID: ” + folder.getId().toString() + newline +
tab + “name: ” + folder.getName() + newline +
tab + “path: ” + folder.getPath();//create new folder in the root folder
Map properties = new HashMap();
properties.put(PropertyIds.OBJECT_TYPE_ID, “cmis:folder”);
properties.put(PropertyIds.NAME, “mynewfolder”);
Folder newFolder = folder.createFolder(properties);//Check properties
String folderprops=twolines + “NEW FOLDER:” + newline +
tab + “ID: ” + newFolder.getId().toString() + newline +
tab + “name: ” + newFolder.getName() + newline +
tab + “path: ” + newFolder.getPath();

//Get another folder by path that already exists; CmisObject does not have a “getPath” method,
//so convert it to a CMIS folder
CmisObject cmisObj = session.getObjectByPath(“/2011″);
Folder fo = (Folder) cmisObj;

//Check properties
folderprops=twolines + “2011 FOLDER:” + newline +
tab + “ID: ” + fo.getId().toString() + newline +
tab + “name: ” + fo.getName() + newline +
tab + “path: ” + fo.getPath();

 

 

Results:

  ROOT FOLDER:
ID: -1
name: Shared Documents
path: /NEW FOLDER:
ID: 87
name: mynewfolder
path: /mynewfolder2011 FOLDER:
ID: 83
name: 2011
path: /2011

Things to note:

1) The path is relative to the Repository, so the “Shared Documents” library could be accessed at its top level with the path “/”.

Iterating over SharePoint Folders

The following exmple will show you how to iterate over the folder objects. An additional class library is imported iterating over CMIS objects.
 

 
import org.apache.chemistry.opencmis.client.api.ItemIterable;

FileList.setText(“FOLDER (Name; ID)” + newline + tab + “CHILDREN (Name; ID)”);folder = session.getRootFolder();
FileList.append(twolines+folder.getName()+”; ” + folder.getId());
tabs = tab;
this.getKids(folder);
}

private void getKids(Folder folder) {
//recursive method to go through library

ItemIterable children = folder.getChildren();
for ( CmisObject obj : children )
{
if(obj.getBaseTypeId().toString()!=”CMIS_FOLDER”)
{
FileList.append(newline+tabs+obj.getName()+”; ” + obj.getId());
}
}
children = folder.getChildren();
for ( CmisObject obj : children )
{
if(obj.getBaseTypeId().toString()==”CMIS_FOLDER”)
{
folder = (Folder) obj;
FileList.append(newline+tabs+”/” + folder.getName()+”; ” + folder.getId());
tabs = tabs + tab;
this.getKids(folder);
}
}
}

Results:

  FOLDER (Name; ID)
CHILDREN (Name; ID)Shared Documents; -1
/2011; 83
/05; 84
/03; 85
 

Things to note:

1) The root ID of SharePoint libraries is always “-1”
2) You can detect and display properties of files within the folders by using the base type “CMIS_DOCUMENT”.
3) The document ID produced from CMIS_DOCUMENT is not the one you see exposed in the SharePoint GUI, but it is the one you use to access a document in CMIS.

Adding a Document to a Folder

Putting documents into a SharePoint folder is typical operation you would need to do. I’ve shown how you get and create a folder, so now you have a place to put your file. A number of additional libraries are needed for this operation.
 

 
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.io.ByteArrayInputStream;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.chemistry.opencmis.client.api.Document;

//details of the fileChooser not shown here. The goal is to get an InputStream
File file = fileChooser.getSelectedFile();
InputStream is = new FileInputStream(file);
long length = file.length();
BigInteger bi = BigInteger.valueOf(length);
ContentStream contentStream = new ContentStreamImpl(file.getName(), bi, “text/plain”, is);
Map properties = new HashMap();
properties.put(PropertyIds.OBJECT_TYPE_ID, “cmis:document”);
properties.put(PropertyIds.NAME, file.getName());folder = session.getRootFolder();
Document doc = folder.createDocument(properties, contentStream, VersioningState.MINOR);

Things to note:

1) This code shows the essentials, but actual implementation should provide for the various exceptions that could arise, CmisConstraintException and IOException for example.
2) New ContentStreamImpl requires a BigInteger, hence the conversion of the file length from long.
3) VersioningState can be MAJOR, MINOR, NONE, or CHECKEDOUT. Whichever you use must match the versioning enabled in the SharePoint library.

I have shown how you can use the Chemistry openCMIS library and REST Atom Service to:
1) Access and Create Folders; 2) Iterate over SharePoint folders; and 3) Add documents to a SharePoint folder.

I hope this will “de-mystify” how to perform common DM operations in SharePoint using the CMIS standard!


Copyright © 2002–2011, Armedia. All Rights Reserved.