Hi there!
We have moved our blog to the following address:
http://blog.ctp.com/category/microsoft-competence/
In case you have any comment, please leave it using the new URL. Soon we will stop monitoring the blog entries in this site. Also our new material will be posted only in the new URL.
Thanks!
Introduction
We have moved our blog to the following address:
http://blog.ctp.com/category/microsoft-competence/
In case you have any comment, please leave it using the new URL. Soon we will stop monitoring the blog entries in this site. Also our new material will be posted only in the new URL.
Thanks!
Introduction
One of the main possibilities that provider hosted
apps in SharePoint 2013 disclose is the possibility of seamless integration
between SharePoint and external systems implemented in non-Microsoft
technologies.
In this post are explored the basics of the integration between
SharePoint on premises and a Java web application. The RESTfull web service API
of SharePoint leverages the communication with the Java web application. The SharePoint chrome
control allows the Java web application to have the same styling of the
hosted web site.
The TokenHelper.cs source file provides the token service
authentication for provided hosted .Net applications. At the moment, it wasn’t
found a Java implementation equivalent to the TokenHelper.cs for SharePoint on
premises. Thus, it will be used NTLM authentication for the web service calls from
the Java web application to SharePoint. This topic shall be revisited.
Find below an extract of the critical parts of the solution
source code.
The Communication Layer with SharePoint
The Java web application reads and writes to a list of cars
in the hosted web. The hosted web is called Interop1 and the list with data is
called Cars. The list Cars has the columns: Brand, Model and Price.
The Java web application will be called Cars App.
The following are the dependencies of the Cars App in its Java implementation:
json-lib 2.4 helper classes to manipulate
Json objects
resteasy-jaxrs
2.2.1.GA RESTEasy
was used to access the RESTfull SharePoint API
commons-httpclient
3.1 dependency of RESTEasy
httpclient 4.1.1 dependency of RESTEasy
spring-web 2.5.6
spring-core 2.5.6
spring-webmvc
2.5.6
commons-logging
1.1.1
The payload class where the data about a car is stored could
be as follows (file Car.java):
public class Car {
private String brand;
private String name;
private long price;
public String getBrand() {
return brand;
}
public void setBrand(String
brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name)
{
this.name = name;
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
}
It has been defined the Car service in order to read and
write into the Cars list. It has the declaration (file CarService.java):
public interface CarService {
public List<Car> getCars() throws CarException,
HttpException, IOException ;
public long insertCar(Car car) throws CarException;
}
The Cars service implementation has the member variables
(file CarServiceImpl.java). These member variables define the authentication
parameters, the URIs for the web service endpoints in SharePoint and the field
names of the Cars list.
String user = "YOUR-USER";
String pass = "YOUR-PASSWORD";
String urlList =
"http://SHAREPOINT-SERVER/sites/Interop1/_api/web/lists/getbytitle('Cars')/items";
String urlDigest = "http://SHAREPOINT-SERVER/sites/Interop1/_api/contextinfo";
// These tags are the ones that SharePoint
uses to identify the columns in the
// list. They can be retrieved by making a HTTP
Get to the server and
// checking the returning field names.
String brandTag = "x3aj";
String modelTag = "Title";
String priceTag = "Price";
The Cars service implements the following helper methods.
The helper method callRestEasyService
reads data by performing a HTTP GET against the RESTfull API of SharePoint. The
URI provided in the req parameter
defines exactly what is being retrieved; it follows the OData protocol syntax.
In our case it would be the items of the list Cars.
The result is returned in JSON format, as stated in “application/json; odata=verbose”. Also,
it would be possible to return the result as XML by setting the format as “application/atom+xml”.
public String callRestEasyService(String
req, String user, String pass) {
String output = "nothing";
try {
// For Kerberos authentication:
// Credentials credentials
=
// new UsernamePasswordCredentials(user, pass);
NTCredentials credentials =
new NTCredentials(user,
pass, "JAVA-MACHINE-NAME", "DOMAIN");
HttpClient httpClient = new HttpClient();
httpClient.getState().setCredentials(AuthScope.ANY, credentials);
httpClient.getParams().setAuthenticationPreemptive(true);
ClientExecutor
clientExecutor =
new
ApacheHttpClientExecutor(httpClient);
java.net.URI uri = new java.net.URI(req);
ClientRequestFactory fac =
new
ClientRequestFactory(clientExecutor, uri);
ClientRequest request = fac.createRequest(req);
request.accept("application/json;odata=verbose");
ClientResponse<String>
response = request.get(String.class);
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP
error code : " +
response.getStatus());
}
BufferedReader br = new BufferedReader(new InputStreamReader(
new
ByteArrayInputStream(response.getEntity().getBytes())));
System.out.println("Output from
Server .... \n");
while ((output =
br.readLine()) != null) {
return output;
}
} catch
(ClientProtocolException e) {
e.printStackTrace();
return e.getMessage();
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
}
catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
return null;
}
In order to create objects in SharePoint through the
RESTfull API (i.e., items in the Cars list), it’s needed to provide in the call
a Form Digest. The Form Digest can be
obtained through a POST to the hosted web endpoint “/_api/contextinfo”. The
helper addRestEasyRetrieveDigest performs
this POST and reads the Form Digest. The exact URI is defined in the variable urlDigest, already declared above.
Note the methods callRestEasyService
and addRestEasyRetrieveDigest are
practically the same, just the first does GET and the second POST.
public String callRestEasyRetrieveDigest
(String req, String user, String pass) {
String output = "nothing";
try {
NTCredentials credentials =
new NTCredentials(user,
pass, "JAVA-MACHINE-NAME", "DOMAIN");
HttpClient httpClient = new HttpClient();
httpClient.getState().setCredentials(AuthScope.ANY, credentials);
httpClient.getParams().setAuthenticationPreemptive(true);
ClientExecutor
clientExecutor =
new
ApacheHttpClientExecutor(httpClient);
java.net.URI uri = new java.net.URI(req);
ClientRequestFactory fac =
new
ClientRequestFactory(clientExecutor, uri);
ClientRequest request =
fac.createRequest(req);
request.accept("application/json;odata=verbose");
ClientResponse<String>
response = request.post(String.class);
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP
error code : " +
response.getStatus());
}
BufferedReader br = new BufferedReader(new InputStreamReader(
new
ByteArrayInputStream(response.getEntity().getBytes())));
System.out.println("Output from
Server .... \n");
while ((output = br.readLine())
!= null) {
return output;
}
} catch
(ClientProtocolException e) {
e.printStackTrace();
return e.getMessage();
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
return null;
}
The helper addRestEasyPost allows the creation of objects in SharePoint through POSTs as stated in the OData protocol. This method is used to add cars to the list Cars.
The parameter req
has the URI where to create the object. Here, it would be the URI of the Cars
list.
Another detail regarding objects creation, it’s the need to
state its type. In the Cars list, the list items are of type
SP.Data.CarsListItem. The type naming convention is as follows:
SP.Data.<ListName>ListItem
In case of doubts, it’s possible to confirm the object type
by performing a HTTP GET for the list items and checking their type.
public String
addRestEasyPost(String req, String user, String pass, String digestValue, Car
car) {
String output = "nothing";
try {
// For Kerberos authentication:
// Credentials credentials =
// new UsernamePasswordCredentials(user,
pass);
NTCredentials credentials =
new NTCredentials(user,
pass, "JAVA-MACHINE-NAME", "DOMAIN");
HttpClient httpClient = new HttpClient();
httpClient.getState().setCredentials(AuthScope.ANY, credentials);
httpClient.getParams().setAuthenticationPreemptive(true);
ClientExecutor
clientExecutor =
new ApacheHttpClientExecutor(httpClient);
java.net.URI uri = new java.net.URI(req);
ClientRequestFactory fac =
new
ClientRequestFactory(clientExecutor, uri);
ClientRequest request =
fac.createRequest(req);
request.accept("application/json;odata=verbose");
request.header("content-type", "application/json;odata=verbose");
request.header("X-RequestDigest", digestValue);
JSONObject innerObject = new JSONObject();
innerObject.put("type", "SP.Data.CarsListItem");
JSONObject body = new JSONObject();
body.put("__metadata", innerObject);
body.put(brandTag, car.getBrand());
body.put(modelTag, car.getName());
body.put(priceTag, car.getPrice());
request.body("application/json", body.toString());
// This attribute was not needed (despite
the documentation):
// request.header("content-length",
// request.getBody().toString().length());
ClientResponse<String>
response = request.post(String.class);
if (response.getStatus() != 201) {
throw new RuntimeException("Failed : HTTP
error code : " +
response.getStatus());
}
BufferedReader br = new BufferedReader(new InputStreamReader(
new
ByteArrayInputStream(response.getEntity().getBytes())));
System.out.println("Output from
Server .... \n");
while ((output = br.readLine()) != null) {
return output;
}
} catch
(ClientProtocolException e) {
e.printStackTrace();
return e.getMessage();
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
return null;
}
The helper method parseJsonDigestValue
parses the JSON response from the endpoint “/_api/contextinfo” retrieving the
form digest.
public static String
parseJsonDigestValue(String json) {
JSONObject jsonObject = (JSONObject)
JSONSerializer.toJSON(json);
jsonObject = (JSONObject)
jsonObject.get("d");
jsonObject = (JSONObject)
jsonObject.get("GetContextWebInformation");
return jsonObject.getString("FormDigestValue");
}
The following helper, parseJson,
translates the JSON response to the Car payload class, returning a list of
cars.
public List<Car> parseJson(String
json) {
List<Car> spCars = new
ArrayList<Car>();
JSONObject jsonObject = (JSONObject)
JSONSerializer.toJSON(json);
jsonObject = (JSONObject)
jsonObject.get("d");
JSONArray array = (JSONArray)
jsonObject.get("results");
Car car;
for (int i = 0; i < array.size(); i++) {
car = new Car();
jsonObject = (JSONObject)
array.get(i);
car.setBrand(jsonObject.getString(brandTag));
car.setName(jsonObject.getString(modelTag));
car.setPrice(jsonObject.getLong(priceTag));
spCars.add(car);
}
return spCars;
}
To read items, the Cars service defines the method getCars. The implementation is as follows:
public List<Car> getCars() throws CarException,
HttpException, IOException {
String jsonString = callRestEasyService(urlList, user, pass);
return parseJson(jsonString);
}
To write items, the Cars service defines the method insertCar. The implementation uses the
helper method as follows:
public long insertCar(Car car) throws CarException {
cars.add(car);
String digestValue =
parseJsonDigestValue(callRestEasyRetrieveDigest(urlDigest, user, pass));
addRestEasyPost(urlList, user, pass, digestValue, car);
return 0;
}
User Experience (UX) for Apps in SharePoint 2013
The App user experience can be designed on top of the
three possibilities within SharePoint:
- Full page user experience
- App Part user experience
- Custom Actions
The full page UX means the App will have the whole browser
page for its user interface. In the full page UX, the App can make use of the SharePoint chrome
control to render the same styling as of the hosted web and, optionally,
render a header like SharePoint pages do.
In the App Part UX the App surfaces its UI on a
page in the hosted web. A special kind of web part known as App Part takes care
of embedding the App's page into the hosted page. The App Part accomplished this by rendering an iFrame where the source is the App's page. The
App page can have the same styling of the hosted web by importing the hosted site's CSS files
through JavaScript.
Custom Actions allow the placement of links to App’s
pages in the Ribbon or in the Edi Control Block of the hosted web. This option
has not been explored in this post.
Implementing the User Interface
The code below implements the App page named Car.jsp; it displays the list of cars. The page can be rendered as in the full
page UX or in the App Part UX. The URL parameter inAppPart defines which way the page
will be rendered (inAppPart=true, then as in the App Part UX; if none or false, then as in the full page UX).
First add a few declarations to the JSP page (file Car.jsp):
<%@ page
language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ taglib
prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib
prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib
prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html
PUBLIC "-//W3C//DTD
HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=ISO-8859-1">
<title><spring:message code="welcome.title" /></title>
The code in the following boxes should be added to the HEAD
element of the page.
Add the references to the libraries in the JSP page (file
Car.jsp):
<script src="http://ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"
type="text/javascript">
</script>
<script type="text/javascript"
src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.7.2.min.js">
</script>
Initialise the JavaScript block and a few variables from the
URL query string:
<script type="text/javascript">
"use strict";
// define a namespace for the JavaScript
objects: Interop
window.Interop = window.Interop || {};
$(document).ready(function() {
// initialisations
Interop.CarsApp.init();
Interop.Styling.init();
});
Interop.CarsApp = {
// load URL parameters
inAppPart: "false",
hostUrl: '',
init: function() {
// if the App page
is in an App Part,
// the developer could set
a URL parameter to inform that.
// Here: inAppPart=true
this.inAppPart =
Interop.CarsApp.getQueryStringParameter("inAppPart");
// the hosted web
URL
this.hostUrl =
decodeURIComponent(
Interop.CarsApp.getQueryStringParameter("SPHostUrl"));
},
// Function to retrieve a query
string value.
// For production purposes you may
want to use
// a library to handle the query
string.
getQueryStringParameter: function (paramToRetrieve) {
var params =
document.URL.split("?")[1].split("&");
var strParams = "";
for ( var i = 0; i < params.length; i = i
+ 1) {
var singleParam =
params[i].split("=");
if
(singleParam[0].toLowerCase() ==
paramToRetrieve.toLowerCase())
return singleParam[1];
}
return "";
}
};
The following script loads the CSS or the chrome control from
SharePoint into the page:
Interop.Styling = {
// Load the SharePoint styling
init: function () {
var inAppPart =
Interop.CarsApp.inAppPart,
hostUrl =
Interop.CarsApp.hostUrl, scriptURI, linkMarkup;
// if the App page
will be displayed as in full page or
// in an App Part, distinct styling
initialisations are done
if(inAppPart === "true") {
// When loading
the App page in an App Part
// (i.e., embedded in the hosted
page)
//
hide a few elements which are just meant for the
// full page user experience
$('.displayJustInFullPage').hide();
//
load the hosted web styling
if (hostUrl !== "") {
linkMarkup = "<link
rel='stylesheet' href='" + hostUrl +
"/_layouts/15/defaultcss.ashx'
/>";
} else {
// if no host web URL is available,
load the default styling
linkMarkup = "<link
rel='stylesheet' " +
"href='/_layouts/15/1033/styles/themable/corev15.css'
/>";
}
$('head').append(linkMarkup);
} else {
//
When loading the App page as in full page user experience
// Load the js file
SP.UI.Controls.js,
// afterwards set the Chrome
Control and styling
scriptURI = hostUrl + "/_layouts/15/SP.UI.Controls.js";
$.getScript(scriptURI,
Interop.Styling.setChromeControl);
}
},
setChromeControl: function () {
//
Set the Chrome control and styling
// (used when the App is displayed
in the full page user experience)
var hostUrl = Interop.CarsApp.hostUrl,
options = {},
nav;
options.siteTitle ="Cars";
options.siteUrl = hostUrl;
options.appHelpPageUrl = "cars.html?" +
document.URL.split("?")[1];
options.appIconUrl = hostUrl + "/Shared
Documents/car.png";
options.appTitle = "Cars
App";
options.settingsLinks = [
{
"linkUrl" : "car.html?" + document.URL.split("?")[1],
"displayName" : "Car
List"
},
{
"linkUrl" : "carForm.html?" +
document.URL.split("?")[1],
"displayName" : "Add a
Car"
}
];
nav = new
SP.UI.Controls.Navigation("chromeControlContainer", options);
nav.setVisible(true);
}
};
</script>
</head>
The following piece of HTML defines the BODY of the page.
The elements which should be seen just in the full page user experience are
marked with the CSS class displayJustInFullPage.
This allows the JavaScript part to hide or display them as seen above.
<body>
<div id='content'>
<div class='displayJustInFullPage'>
<!-- Chrome
control placeholder -->
<div id="chromeControlContainer"></div>
<h1>Cars List</h1>
</div>
<table>
<tr class="ms-viewheadertr
ms-vhltr">
<th class="ms-vh2">Brand</th>
<th class="ms-vh2">Model</th>
<th class="ms-vh2">Price</th>
</tr>
<c:forEach items="${cars}"
var="car">
<tr>
<td class="ms-vb2">${car.brand}</td>
<td class="ms-vb2">${car.name}</td>
<td class="ms-vb2">${car.price}</td>
<tr>
</c:forEach>
</table>
</div>
</body>
</html>
The declarations above should be applied on every page that
should look like the hosted web.
The Provider Hosted App
A SharePoint provider hosted app should be created following
the instructions:
Once created, edit the AppManifest.xml,
setting the URL to a page in your Java web application. That could be the entry
page of your application for the full page user experience:
<Properties>
<Title>YOUR-APP-TITLE</Title>
<StartPage>http://JAVA-WEB-APPLICATION-URL/car.html?{StandardTokens}</StartPage>
</Properties>
The App Part
For the App Part user experience, first add an App Part
(known as well as Client Web Part) to your SharePoint project. After that, in
the Elements.xml of the App Part, set
the URL to the Java web application:
<Content
Type="html" Src="http://JAVA-WEB-APPLICATION-URL/car.html?{StandardTokens}&inAppPart=true"
/>
It is important to set there "inAppPart=true" to ensure the App Part user experience.
For instructions about how to create an App Part with Visual
Studio 2012, refer to:
Conclusion
Provider hosted apps unleash great possibilities of
integration between SharePoint and systems developed in other platforms as
Java. Here, we have seen how to perform basic read/write operations on SharePoint
lists. Also, it was shown how to integrate the App at user interface level with
the full page and App Part user experiences.
The SharePoint list Cars; data is read and written into it by the Java
web application.
In the full page user
experience, the Java web application displaying the cars with the same styling as
SharePoint.
From the menu item “Add
a Car” it is possible to reach the page from where new cars can be added.
The form to add new cars into the SharePoint list.
Could you tell which list in from the Java web application and which is from SharePoint?
On the left, the App Part displaying the Java web application and on
the right the SharePoint list.
A point of improvement is that App Parts do not
resize automatically to fit the Apps content. If the content grows beyond the
App Part area in the hosted page, scroll bars are rendered. However, the App page can
control the resizing of the App Part avoiding the scroll bars. This is
explained in this post:
Further References
- Programming using the SharePoint 2013 REST service
- How to: Complete basic operations using SharePoint 2013 REST endpoints
- How to: Use the client chrome control in apps for SharePoint
- RESTEasy - JBoss Community
Authors: Leandro Bernsmüller and Angel Torralba