Google Gadget and OpenSocial Integration
Google Gadget and OpenSocial Integration (GSoC 2009)
Abstract
Gadgets integration with XWiki will allow users to quickly add any gadget from Google's Gadget Directory to any wiki page or the side panels. Gadgets offer the most diverse functionalities and will benefit many of the unanticipated user needs. The OpenSocial Integration will add a more social dimension to XWiki by hosting OpenSocial apps, thus becoming the first wiki OpenSocial Container. Anyone will be able to install XWiki and instantly create their own social collaborative orientated network.Oct 29 2009
Drag&Drop iGoogle like Dashboard
I've been playing around with Smartclient to create an iGoogle like Dashboard with drag&drop windows for the Apps. You can play with it here:
App Dashboard
Aug 24 2009
Files listing
Changes on xwiki-web-standard module (patch)
These changes comprise new velocity files, Shindig JS files, Shindig container configurations, modifications to module's web.xml and pom.xml files.in standard/src/main/webapps/templates:
Additional Velocity files:- addapplication.vm - installs an application from the directory for a user; the result is a document with an OpenSocialApplication.ApplicationClass attached to it, in the Space OpenSocialApplication; also specific rights are set for the document: all to self, some to admin, only view to others
- getuserprefs.vm - returns a JSON with all the OpenSocial User Preferences set/saved by a specific gadget/application; the UserPrefs are key-value pairs, and are stored as OpenSocial.UserPrefClass objects attached to installed application pages
- saveuserprefs.vm - saves new User Prefs for a application and user
- addfriend.vm - adds a friend for a user; friends are XWiki.FriendClass objects attached to a user's profile page. XWiki.FriendClass objects have a property called friendName wich keeps the document full name of the user friend
- getuserfriends.vm - returns the friends of a user
- deletefriend.vm - deletes a friend
in standard/src/main/webapps/resources/xwiki/opensocial:
Added javascript and css files necessary for the Shindig client container:- gadgets.js
- rpc.js
- util.js
- json.js
- gadgets.css
in standard/src/main/webapps/WEB-INF:
Modified web.xml to include all shindig servlet mappings and necessary Guice modules:- Shindig Guice modules:
<!-- Shidig Guice modules --> <context-param> <param-name>guice-modules</param-name> <param-value> org.apache.shindig.common.PropertiesModule: org.apache.shindig.gadgets.DefaultGuiceModule: org.apache.shindig.gadgets.oauth.OAuthModule: org.apache.shindig.common.cache.ehcache.EhCacheModule: org.xwiki.opensocial.social.XWSocialModule </param-value> </context-param>
- Shindig filters:
<!-- Shindig filters --> <filter> <filter-name>authFilter</filter-name> <filter-class>org.apache.shindig.auth.AuthenticationServletFilter</filter-class> </filter>
- Shindig filter mappings:
<!-- Shindig filter mappings --> <filter-mapping> <filter-name>authFilter</filter-name> <url-pattern>/social/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>authFilter</filter-name> <url-pattern>/gadgets/ifr</url-pattern> </filter-mapping> <filter-mapping> <filter-name>authFilter</filter-name> <url-pattern>/gadgets/makeRequest</url-pattern> </filter-mapping> <filter-mapping> <filter-name>authFilter</filter-name> <url-pattern>/gadgets/api/rpc/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>authFilter</filter-name> <url-pattern>/gadgets/api/rest/*</url-pattern> </filter-mapping>
- Shindig Guice listener:
<!-- Shindig Guice listener --> <listener> <listener-class>org.apache.shindig.common.servlet.GuiceServletContextListener</listener-class> </listener>
- Shindig Servlets:
<!-- Render a Gadget --> <servlet> <servlet-name>xml-to-html</servlet-name> <servlet-class> org.apache.shindig.gadgets.servlet.GadgetRenderingServlet </servlet-class> </servlet> <!-- Proxy --> <servlet> <servlet-name>proxy</servlet-name> <servlet-class> org.apache.shindig.gadgets.servlet.ProxyServlet </servlet-class> </servlet> <!-- makeRequest --> <servlet> <servlet-name>makeRequest</servlet-name> <servlet-class> org.apache.shindig.gadgets.servlet.MakeRequestServlet </servlet-class> </servlet> <servlet> <servlet-name>concat</servlet-name> <servlet-class> org.apache.shindig.gadgets.servlet.ConcatProxyServlet </servlet-class> </servlet> <!-- OAuth callback --> <servlet> <servlet-name>oauthCallback</servlet-name> <servlet-class> org.apache.shindig.gadgets.servlet.OAuthCallbackServlet </servlet-class> </servlet> <!-- Metadata RPC --> <servlet> <servlet-name>metadata</servlet-name> <servlet-class> org.apache.shindig.gadgets.servlet.RpcServlet </servlet-class> </servlet> <!-- javascript serving --> <servlet> <servlet-name>js</servlet-name> <servlet-class>org.apache.shindig.gadgets.servlet.JsServlet</servlet-class> </servlet> <!-- Serve social REST api --> <servlet> <servlet-name>socialRestapiServlet</servlet-name> <servlet-class> org.apache.shindig.protocol.DataServiceServlet </servlet-class> <init-param> <param-name>handlers</param-name> <param-value>org.apache.shindig.social.handlers</param-value> </init-param> </servlet> <!-- Serve social RPC api --> <servlet> <servlet-name>socialJsonRpcServlet</servlet-name> <servlet-class> org.xwiki.opensocial.social.XWikiShindigServlet </servlet-class> <init-param> <param-name>handlers</param-name> <param-value>org.apache.shindig.social.handlers</param-value> </init-param> </servlet> <!-- Serve gadgets RPC api --> <servlet> <servlet-name>gadgetsJsonRpcServlet</servlet-name> <servlet-class> org.apache.shindig.protocol.JsonRpcServlet </servlet-class> <init-param> <param-name>handlers</param-name> <param-value>org.apache.shindig.gadgets.handlers</param-value> </init-param> </servlet> <!-- Serve gadgets REST api --> <servlet> <servlet-name>gadgetsRestapiServlet</servlet-name> <servlet-class> org.apache.shindig.protocol.DataServiceServlet </servlet-class> <init-param> <param-name>handlers</param-name> <param-value>org.apache.shindig.gadgets.handlers</param-value> </init-param> </servlet>
- Shindig Servlet mappings:
<!-- Shindig servlet mappings --> <servlet-mapping> <servlet-name>js</servlet-name> <url-pattern>/gadgets/js/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>proxy</servlet-name> <url-pattern>/gadgets/proxy/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>makeRequest</servlet-name> <url-pattern>/gadgets/makeRequest</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>gadgetsJsonRpcServlet</servlet-name> <url-pattern>/gadgets/api/rpc/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>gadgetsRestapiServlet</servlet-name> <url-pattern>/gadgets/api/rest/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>concat</servlet-name> <url-pattern>/gadgets/concat</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>oauthCallback</servlet-name> <url-pattern>/gadgets/oauthcallback</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>xml-to-html</servlet-name> <url-pattern>/gadgets/ifr</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>metadata</servlet-name> <url-pattern>/gadgets/metadata</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>socialRestapiServlet</servlet-name> <url-pattern>/social/rest/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>socialJsonRpcServlet</servlet-name> <url-pattern>/social/rpc/*</url-pattern> </servlet-mapping>
in standard/src/main/webapps/WEB-INF/classes:
Added container/default/container.js and shindig.properties - shindig container configurations files. I modified the base URLs for the services provided by Shindig, so they all contain /xwiki/.For example, http://%host%/gadgets/api/rpc becomes http://%host%/xwiki/gadgets/api/rpc.
I placed the 2 files in the classpath, as they required modifications from the original files.
in standard/src/main/webapps/resources/icons/silk:
- os_app.js - added an icon for the gadgets (used in css)
in standard:
Modified pom.xml, to include as a dependency xwiki-social-opensocial module<!-- The OpenSocial module --> <dependency> <groupId>org.xwiki.platform</groupId> <artifactId>xwiki-social-opensocial</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
XClasses and documents created (.XAR Application)
- OpenSocial.AppDataClass - Application data created by applications for a specific user. Objects of this type are saved as attachments on installed application pages (pages containing objects of type OpenSocialApplication.ApplicationClass); App data is saved in the form of key-value pairs
- OpenSocial.AppDataClassSheet - default content
- OpenSocial.AppDataClassTemplate - default content
- OpenSocial.Dashboard - Displays applications all installed applications for the logged in user in the profile/default view form; The page script includes shindig JS files, retrieves all apps of interest, adds them to the container, and renders them:
$xwiki.jsfx.use("js/xwiki/opensocial/json.js") $xwiki.jsfx.use("js/xwiki/opensocial/rpc.js") $xwiki.jsfx.use("js/xwiki/opensocial/cookies.js") $xwiki.jsfx.use("js/xwiki/opensocial/util.js") $xwiki.jsfx.use("js/xwiki/opensocial/gadgets.js") $xwiki.ssfx.use("js/xwiki/opensocial/gadgets.css") $xwiki.jsx.use("OpenSocial.Dashboard") … ## Get all viewer apps #set($sql = ", BaseObject obj, StringProperty prop where doc.fullName=obj.name and obj.className='OpenSocialApplication.ApplicationClass' and prop.id.id=obj.id and prop.name='userId' and prop.value='$context.user' order by doc.date desc") … <div id="layout-root" class="gadgets-layout-root"></div> … <script> … document.observe('xwiki:dom:loaded', function() { init(myapps); renderGadgets(); }); </script>
/** XWiki Ifr Dashboard Gadget **/
XWikiDashboardGadget = function(opt_params) {
gadgets.IfrGadget.call(this, opt_params);
};
XWikiDashboardGadget.inherits(gadgets.IfrGadget);
XWikiDashboardGadget.prototype.handleMaximize = function() {
window.location = this.fullAppUrl;
};
XWikiDashboardGadget.prototype.getTitleBarContent = function(continuation) {
continuation('<div id="' + this.cssClassTitleBar + '-' + this.id +
'" class="' + this.cssClassTitleBar + '"><span id="' +
this.getIframeId() + '_title" class="' +
this.cssClassTitle + '">' + (this.title ? this.title : 'Title') + '</span> | <span class="' +
this.cssClassTitleButtonBar +
'"><a href="#" onclick="gadgets.container.getGadget(' + this.id +
').handleOpenUserPrefsDialog();return false;" class="' + this.cssClassTitleButton +
'">settings</a> <a href="#" onclick="gadgets.container.getGadget(' +
this.id + ').handleToggle();return false;" class="' + this.cssClassTitleButton +
'">toggle</a> <a href="#" onclick="gadgets.container.getGadget(' +
this.id + ').handleMaximize();return false;" class="' + this.cssClassTitleButton +
'">see full app</a></span></div>');
};
gadgets.container.gadgetClass = XWikiDashboardGadget;
/** XWiki persistent UserPrefStore **/
gadgets.XWikiUserPrefStore = function() {
gadgets.UserPrefStore.call(this);
};
gadgets.XWikiUserPrefStore.inherits(gadgets.UserPrefStore);
gadgets.XWikiUserPrefStore.prototype.savePrefs = function(gadget) {
var pairs = [];
for (var name in gadget.getUserPrefs()) {
var value = gadget.getUserPref(name);
var pair = encodeURIComponent(name) + '=' + encodeURIComponent(value);
pairs.push(pair);
}
var params = pairs.join('&');
// store user prefs parameters
var url = gadget.fullAppUrl + "?xpage=saveuserprefs" + "&" + "params=" + encodeURIComponent(params);
new Ajax.Request(url,
{
method: 'get',
onSuccess: savePrefsAjaxSuccess
});
};
gadgets.XWikiUserPrefStore.prototype.getPrefs = function(gadget) {
var userPrefs = {};
var url = gadget.fullAppUrl + "?xpage=getuserprefs";
new Ajax.Request(url,
{
method: 'get',
onSuccess: getPrefsAjaxSuccess
});
return userPrefs;
};
gadgets.Container.prototype.userPrefStore = new gadgets.XWikiUserPrefStore();
/** Initialize XWiki Dashboard Container **/
function init(apps) {
gadgets.container.layoutManager = new gadgets.FloatLeftLayoutManager('layout-root');
for (var specUrl in apps) {
gadgets.container.addGadget(gadgets.container.createGadget({specUrl: specUrl, title: apps[specUrl].title, fullAppUrl: apps[specUrl].fullAppUrl}));
}
};
/** Render all gadgets on Dashboard **/
function renderGadgets() {
gadgets.container.renderGadgets();
};- OpenSocial.Stylesheet - a bit of CSS
- OpenSocial.TestingGadgetsSource - used to attach gadgets XML files to the page. Their URLs can be used when adding applications to the Directory
- OpenSocial.UserPrefClass - stores User Preferences for each application and user in the form of key-value pais
- OpenSocial.UserPrefClassSheet - default content
- OpenSocial.UserPrefClassTemplate - default content
- OpenSocialAppInfo.AppInfoClass - Defines an application from the Directory; contains fields like: URL to Gadget XML definition file, name, description (should also contain image and icon URLs, authors, etc.)
- OpenSocialAppInfo.AppInfoClassSheet - Displays an application from the Directory. You have links to install the application for yourself, or to manage/delete if you already have it installed; You can also see the Friends which have the same application installed.
#set($appId = $doc.fullName)
#set($sql = "select fnp.value from XWikiDocument adoc, XWikiDocument udoc, BaseObject fo, BaseObject ao, StringProperty fnp, StringProperty uidp, StringProperty aidp where adoc.fullName=ao.name and udoc.fullName=fo.name and udoc.fullName='$context.user' and ao.className='OpenSocialApplication.ApplicationClass' and fo.className='XWiki.FriendClass' and uidp.id.id=ao.id and aidp.id.id=ao.id and fnp.id.id=fo.id and uidp.name='userId' and aidp.name='appId' and aidp.value='$appId' and fnp.name='friendName' and fnp.value=uidp.value")
#set($results = $xwiki.search($sql))- OpenSocialAppInfo.AppInfoClassTemplate - default content
- OpenSocialAppInfo.WebHome - Application Directory. Lists all apps in the Directory and provides a form for adding new ones.
- OpenScocialApplication.ApplicationClass - Defines an installed application; the two properties name the application id (addId) - document full name for the application in the directory (a document with OpenSocialAppInfo.AppInfoClass object attached), and the user id (userId) - user document full name
- OpenScocialApplication.ApplicationClassSheet - Renders the application in Canvas view format. Like OpenSocial.Dashboard, includes the Shindig JS files, but renders only 1 gadget this time, and in the canvas format. This gadget is of type XWikiCanvasGadget (extends gadgets.IfrGadget) and is defined in a JSX object attached to the document.
XWikiCanvasGadget = function(opt_params) {
gadgets.IfrGadget.call(this, opt_params);
};
XWikiCanvasGadget.inherits(gadgets.IfrGadget);
XWikiCanvasGadget.prototype.getTitleBarContent = function(continuation) { continuation('');};
XWikiCanvasGadget.prototype.getUserPrefsDialogContent = function(continuation) { continuation('');};
gadgets.container.gadgetClass = XWikiCanvasGadget;
function canvasRenderGadget(specUrlVal) {
var app = gadgets.container.createGadget({specUrl: specUrlVal, width: "100%", height: "600"});
gadgets.container.setView("canvas");
gadgets.container.addGadget(app);
gadgets.container.layoutManager.setGadgetChromeIds(['gadget-chrome']);
gadgets.container.renderGadget(app);
}- OpenScocialApplication.ApplicationClassTemplate - default content
- OpenScocialApplication.MyApps - Lists all installed applications, with buttons to manage/remove. Also there is a button to Add more apps.
- Panels.MyApps - Panel, displays the newest installed apps (max 5) for the currently logged in user; links to dashboard, edit apps, directory
- Panels.MyFriends - Panel, displays the friends (max 4) with profile picture of the currently logged in user; links to Profile page for whole list
- Panels.Navigation - Added the 2 panels
- XWiki.FriendClass - A friend; objects of this are attached to user profiles; they have a single property friendName, which contains the friends' document full name
- XWiki.FriendClassSheet - Uses a livetable to display/add friends
- XWiki.FriendClassTemplate - default content
- XWiki.MyResources
- XWiki.XWikiPreferences - Modified
- XWiki.XWikiUserSheet - Modified to include listing of friends (XWiki.FriendClassSheet) and list applications
xwiki-social-opensocial module
Implements Shindig services and connects Shindig to XWiki's datastore. The implementation is based on the DocumentAccessBridge component, which provides access to data on XWiki. The implementation is still in an incipient phase, as the registration to XWiki's Component Manager didn't work out as expected. The conflict emerges from the fact that the 4 services (PersonServiceXW, ActivityServiceXW, MessageServiceXW, PersonServiceXW etc.) are instantiated both with Guice for Sindig and by XWiki's Component Manager. For more information, see discussion here: http://markmail.org/thread/zxlnu7q67fxzrjmkModule details
- groupId : org.xwiki.platform
- artifactId : xwiki-opensocial-parent
- version: 1.0-SNAPSHOT
- SVN Sandbox: https://svn.xwiki.org/svnroot/xwiki/sandbox/gsoc/opensocial/
Packages and Classes:
- org.xwiki.opensocial.social
- XWSocialModule.java
- org.xwiki.opensocial.social.model
- AddressXW.java
- NameXW.java
- PersonXW.java
- PersonXWComparator.java
- org.xwiki.opensocial.social.oauth
- SocialServiceComponent.java - XWiki Component Role, implemented by ActivityServiceXW, AppDataServiceXW, MessageServiceXW, PersonServiceXW in order to gain access to the DocumentAccessBridge component and fetch/add/delete data
- org.xwiki.opensocial.social.spi.internal
- ActivityServiceXW.java
- AppDataServiceXW.java
- MessageServiceXW.java
- PersonServiceXW.java - Implements Shindig's PersonService, which fetches people from the datastore
Screenshots
My Friends Panel
Displays maximum 4 friends of the currently logged in user
My Apps Panel
Displays most recent 5 installed applications by the currently logged in user
Edit Applications
You can manage your installed applications: edit app settings or remove
Application Directory
You can browse the directory and install any application available. If not, you can add new applications to the directory by providing the XML URL for the Application/Gadget
Application Info Page
Presents information about a certain Gadget/Application in the Directory. You can install it (if you don't already have it installed or edit/remove it if you have it installed). The page also displays which of your friends has it installed
Dashboard
Provides access to all installed gadgets/apps in default/profile view format
Application Canvas view
Application page for canvas view. You can also see the installed applications of your friends (the information inside will be displayed as for a simple viewer, and not the owner)
User Profile
Added lists of friends and applications to profile
May 25 2009
Setting Up the Project with Maven
Yesterday we got our own space on the XWiki sandbox. My space will be here: https://svn.xwiki.org/svnroot/xwiki/sandbox/gsoc/opensocial
We'll be using Maven as a project management tool, so I started with the Maven in 5 Minutes guide to see how it works. I have used Maven before only for building projects (like XWiki Core / Enterprise with the mvn install command) but nothing else.
Create the Project
So, I've created a Maven Project using the archetype:create goal (archetype is the Maven plugin and create the Maven goal - a plugin is a collection of goals)mvn archetype:create -DgroupId=org.xwiki.platform -DartifactId=xwiki-opensocialMy project now has this simple, nice and perfect structure (soon to be messed up :) ) :
xwiki-opensocialThe src/main/java directory contains the project source code, the src/test/java directory contains the test source, and the pom.xml is the project's Project Object Model, or POM. The POM is also nice and simple:
| --pom.xml
`-- src
| --main
| `-- java
| ` --org
| `-- xwiki
| ` --opensocial
| `-- App.java
` --test
`-- java
` --org
`-- xwiki
` --opensocial
`-- AppTest.java
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-opensocial</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>xwiki-opensocial</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Build the Project
mvn packageAnd I already build my project for the first time :) !! It also run 1 test :) and build the 1.0-SNAPSHOT jar file.
...package is a phase, not a goal. A phase is a step in the build lifecycle, which is an ordered sequence of phases. Now let's test the JAR:
[INFO] Building jar: /Users/anamariastoica/Documents/workspace/xwiki-opensocial/target/xwiki-opensocial-1.0-SNAPSHOT.jar
[INFO]----------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO]----------------------------------------------------------------
[INFO] Total time: 7 seconds
[INFO] Finished at: Mon May 25 14:41:51 EEST 2009
[INFO] Final Memory: 8M/17M
[INFO]
10-5-5-101:xwiki-opensocial anamariastoica$ java -cp target/xwiki-opensocial-1.0-SNAPSHOT.jar org.xwiki.opensocial.App
Hello World!
Import the Project in Eclipse
Next step was to import the project in Eclipse, and test the SVN by committing the project.Committed revision 20406.And here it is : https://svn.xwiki.org/svnroot/xwiki/sandbox/gsoc/opensocial/xwiki-opensocial/
More about Maven Phases
As this is new for me and seems important, I will take a few more notes about it. A build lifecycle is made up of phases. There are three built-in build lifecycles: default (handles project deployment), clean (handles project cleaning) and site (handles creation of project's site documentation), each of them having a different list of build phases. For example, the default lifecycle has the following build phases:- validate - validate the project is correct and all necessary information is available
- compile - compile the source code of the project
- test - test the compiled source code using a suitable unit testing framework. These tests should not require the code be packaged or deployed
- package - take the compiled code and package it in its distributable format, such as a JAR.
- integration-test - process and deploy the package if necessary into an environment where integration tests can be run
- verify - run any checks to verify the package is valid and meets quality criteria
- install - install the package into the local repository, for use as a dependency in other projects locally
- deploy - done in an integration or release environment, copies the final package to the remote repository for sharing with other developers and projects.
mvn clean installThis command will traverse into all of the subprojects and run clean, then install (including all of the prior steps).