You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2010/10/07 17:54:40 UTC

svn commit: r1005506 [4/45] - in /shindig/branches/2.0.x: ./ content/editor/ content/editor/CodeMirror-0.8/ content/editor/CodeMirror-0.8/css/ content/editor/CodeMirror-0.8/js/ content/samplecontainer/examples/media/ etc/ etc/checkstyle/ extras/src/mai...

Added: shindig/branches/2.0.x/content/samplecontainer/examples/media/MediaUI.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/samplecontainer/examples/media/MediaUI.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/samplecontainer/examples/media/MediaUI.js (added)
+++ shindig/branches/2.0.x/content/samplecontainer/examples/media/MediaUI.js Thu Oct  7 15:54:09 2010
@@ -0,0 +1,524 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+/*
+ * The User Interface for the Albums & MediaItems gadget.
+ *
+ * SHINDIG TODOS
+ *  set ownerId automatically?
+ *  delete children mediaitems when album deleted?
+ *  update only updates given fields?
+ *  update album mediaitem count when inserting/removing mediaitem?
+ *
+ * GADGET TODOS
+ *  album info such as how many albums are contained
+ *  fix auto height for edit album popup
+ *  thumnail pictures
+ */
+function MediaUI(social) {
+    var viewer = null;
+    var divManager = null;
+    
+    var folderUrl = "http://www.clker.com/cliparts/2/b/b/3/1194983972976950993blue_folder_seth_yastrov_01.svg.med.png";
+    var docUrl = "http://www.plastyc.com/images/document-icon.png";
+    
+    /*
+     * Initializes the gadget.
+     */
+    this.init = function() {
+        console.log("initializing AlbumsUI");
+        
+        // Manages high-level divs
+        divManager = new DivManager();
+        divManager.init();
+        
+        // Load data and render
+        loadData(function() {
+            social.getAlbumsByUser(viewer.id, function(response) {
+                renderAlbums(response.list);
+                divManager.showAlbums();
+            });
+        });
+    }
+    
+    /*
+     * Pre-load data for gadget.
+     */
+    function loadData(callback) {
+        social.getViewer(function(data) {
+            viewer = data;
+            callback();
+        });
+    }
+    
+    /*
+     * Manages the gadgets main DIV elements.
+     * 
+     * TODO: use dojo.query() & classes rather than divs[]
+     * TODO: showOnly() function to avoid flashing/pauses
+     */
+    function DivManager() {
+        var divs = [];
+    
+        this.init = function() {
+            console.log('DivManager.init');
+            addDiv('albumsDiv');
+            addDiv('mediaItemsDiv');
+            addDiv('mediaItemDiv');
+            hideAll();
+        }
+    
+        this.showAlbums = function() {
+            console.log('DivManager.showAlbums');
+            hideAll();
+            divs['albumsDiv'].style.display = 'block';
+            this.refreshWindow();
+        }
+        
+        this.showMediaItems = function() {
+            console.log('DivManager.showMediaItems');
+            hideAll();
+            divs['mediaItemsDiv'].style.display = 'block';
+            this.refreshWindow();
+        }
+        
+        this.showMediaItem = function() {
+            console.log('DivManager.showMediaItem');
+            hideAll();
+            divs['mediaItemDiv'].style.display = 'block';
+            this.refreshWindow();
+        }
+        
+        this.refreshWindow = function() {
+            gadgets.window.adjustHeight(500);
+        }
+        
+        function hideAll() {
+            for (key in divs) { divs[key].style.display = 'none'; }
+        }
+        
+        function addDiv(id) { divs[id] = dojo.create('div', {id: id}, dojo.body()); }
+    }
+    
+    /*
+     * Renders a list of the given albums.
+     */
+    function renderAlbums(albums) {
+        console.log('renderAlbums');
+
+        dojo.empty('albumsDiv');
+        var albumsDiv = dojo.byId('albumsDiv');
+        
+        var albumsBanner = dojo.create('div', null, albumsDiv);
+        var table = dojo.create('table', null, albumsBanner);
+        var tbody = dojo.create('tbody', null, table);
+        var tr = dojo.create('tr', null, tbody);
+        dojo.create('td', {innerHTML: viewer.name.formatted + "'s Albums", className: 'albumsTitle'}, tr);
+        dojo.create('td', null, tr).appendChild(new dijit.form.Button({label: '+ New Album', onClick: dojo.hitch(this, editAlbumPopup, null)}).domNode);
+        
+        var albumsList = dojo.create('div', null, albumsDiv);
+        if (albums.length > 0) {
+            var table = dojo.create('table', {className: 'albumsTable'}, albumsList);
+            var tbody = dojo.create('tbody', null, table);
+            for (i = 0; i < albums.length; i++) {
+                var albumRow = dojo.create('tr', null, tbody);
+                var albumLeft = dojo.create('td', {className: 'albumListThumbnail'}, albumRow);
+                var imgLink = dojo.create('a', {href: "javascript:;", onclick: dojo.hitch(this, onClickAlbum, viewer.id, albums[i])}, albumLeft);
+                dojo.create('img', {src: albums[i].thumbnailUrl, onerror: "this.src='" + folderUrl + "';", width: '100%'}, imgLink);
+                var albumRight = dojo.create('td', {className: 'albumListRight'}, albumRow);
+                var albumTitleRow = dojo.create('tr', null, albumRight);
+                var titleTd = dojo.create('td', {className: 'albumListTitle'}, albumTitleRow); 
+                dojo.create('a', {innerHTML: albums[i].title, href: 'javascript:;', onclick: dojo.hitch(this, onClickAlbum, viewer.id, albums[i])}, titleTd);
+                var editTd = dojo.create('td', {className: 'actionLinks', style: 'text-align: right'}, albumTitleRow);
+                dojo.create('a', {innerHTML: 'edit', href: 'javascript:;', onclick: dojo.hitch(this, editAlbumPopup, albums[i])}, editTd);
+                editTd.appendChild(dojo.doc.createTextNode(' | '));
+                dojo.create('a', {innerHTML: 'delete', href: 'javascript:;', onclick: dojo.hitch(this, deleteAlbumPopup, albums[i])}, editTd);
+                if (albums[i].description) {
+                    var albumDescription = dojo.create('tr', null, albumRight);
+                    dojo.create('td', {innerHTML: albums[i].description, className: 'albumListDescription', colspan: '2'}, albumDescription);
+                }
+                //var albumInfo = dojo.create('tr', null, albumRight);
+                //var infoStr = "ID: " + albums[i].id + " | Owner ID: " + albums[i].ownerId;
+                //dojo.create('td', {innerHTML: infoStr, className: 'albumListInfo', colspan: '2'}, albumInfo);
+            }
+        } else {
+            albumsDiv.appendChild(dojo.doc.createTextNode("No albums found."));
+        }
+        divManager.refreshWindow();
+        
+        // Handles when user clicks an album
+        function onClickAlbum(userId, album) {
+            social.getMediaItemsByAlbum(userId, album.id, function(response) {
+                renderMediaItems(album, response.list);
+                divManager.showMediaItems();
+            });
+        }
+    }
+    
+    /*
+     * Convenience function to retrieve albums and render.
+     */
+    function renderAlbumsByUser(userId, callback) {
+        social.getAlbumsByUser(userId, function(response) {
+            renderAlbums(response.list);
+            divManager.showAlbums();
+            if (callback != null) callback();
+        });
+    }
+    
+    /*
+     * Renders a grid of the given MediaItems.
+     * 
+     * TODO: simplify this by simply taking in 'album', retrieving MediaItems here
+     */
+    function renderMediaItems(album, mediaItems) {
+        console.log('renderMediaItems');
+        dojo.empty('mediaItemsDiv');
+        var mediaItemsDiv = dojo.byId('mediaItemsDiv');
+        var numCols = 5;
+        
+        // Div to display navation bar and Create button
+        var topDiv = dojo.create('div', null, mediaItemsDiv);
+        var table = dojo.create('table', null, topDiv);
+        var tbody = dojo.create('tbody', null, table);
+        var tr = dojo.create('tr', null, tbody);
+        var td = dojo.create('td', {style: 'width:100%'}, tr);
+        dojo.create('a', {innerHTML: 'Albums', href: 'javascript:;', onclick: dojo.hitch(this, renderAlbumsByUser, viewer.id, null)}, td);
+        td.appendChild(dojo.doc.createTextNode(' > ' + album.title));
+        td = dojo.create('td', {style: 'width:100%'}, tr);
+        var createButton = new dijit.form.Button({label: '+ New MediaItem', onClick: dojo.hitch(this, editMediaItemPopup, album, null)});
+        td.appendChild(createButton.domNode);
+        
+        // Div to display MediaItems in a grid
+        var gridDiv = dojo.create('div', null, mediaItemsDiv);
+        if (mediaItems.length > 0) {
+            var table = dojo.create('table', null, gridDiv);
+            var tbody = dojo.create('tbody', null, table);
+            var tr = null;
+            for (i = 0; i < mediaItems.length; i++) {
+                if (i % numCols == 0) {
+                    tr = dojo.create('tr', null, tbody);
+                }
+                var td = dojo.create('td', {className: 'mediaItemBox'}, tr);
+                var imageTd = dojo.create('tr', null, td).appendChild(dojo.create('td', {className: 'mediaItemThumbnail'}));
+                if (mediaItems[i].url) {
+                    var imageLink = dojo.create('a', {href: "javascript:;", onclick: dojo.hitch(this, renderMediaItem, album, mediaItems[i])}, imageTd);
+                    imageLink.appendChild(dojo.create('img', {src: mediaItems[i].thumbnailUrl, onerror: "this.src='" + docUrl + "';", style:'height:100px;'}));
+                } else {
+                    dojo.create('img', {src: mediaItems[i].thumbnailUrl, onerror: "this.src='" + docUrl + "';", style:'height:100px;'}, imageTd);
+                }
+                var titleTd = dojo.create('tr', null, td).appendChild(dojo.create('td', {style: "text-align:center; font-family:'comic sans ms';white-space:nowrap;"}));
+                titleTd.appendChild(dojo.doc.createTextNode(mediaItems[i].title));
+                var actionsTd = dojo.create('tr', null, td).appendChild(dojo.create('td', {className: 'actionLinks', style: 'text-align: center;'}));
+                dojo.create('a', {innerHTML: 'edit', href: 'javascript:;', onclick: dojo.hitch(this, editMediaItemPopup, album, mediaItems[i])}, actionsTd);
+                actionsTd.appendChild(dojo.doc.createTextNode(' | '));
+                dojo.create('a', {innerHTML: 'delete', href: 'javascript:;', onclick: dojo.hitch(this, deleteMediaItemPopup, album, mediaItems[i])}, actionsTd);
+            }
+        } else {
+            gridDiv.appendChild(dojo.doc.createTextNode('Album is empty'));
+        }
+        divManager.refreshWindow();
+    }
+    
+    /*
+     * Convenience function to retriev & render MediaItems by Album.
+     */
+    function retrieveAndRenderMediaItems(album) {
+        social.getMediaItemsByAlbum(viewer.id, album.id, function(response) {
+            divManager.showMediaItems();
+            renderMediaItems(album, response.list);
+        });
+    }
+    
+    /*
+     * Renders the view for a single MediaItem.
+     */
+    function renderMediaItem(album, mediaItem) {
+        console.log('renderMediaItem');
+        dojo.empty('mediaItemDiv');
+        var mediaItemDiv = dojo.byId('mediaItemDiv');
+        
+        // Div to display navation bar and Create button
+        var topDiv = dojo.create('div', null, mediaItemDiv);
+        var table = dojo.create('table', null, topDiv);
+        var tbody = dojo.create('tbody', null, table);
+        var tr = dojo.create('tr', null, tbody);
+        var td = dojo.create('td', {style: 'width:100%'}, tr);
+        dojo.create('a', {innerHTML: 'Albums', href: 'javascript:;', onclick: dojo.hitch(this, renderAlbumsByUser, viewer.id, null)}, td);
+        td.appendChild(dojo.doc.createTextNode(" > "));
+        dojo.create('a', {innerHTML: album.title, href: "javascript:;", onclick: dojo.hitch(this, retrieveAndRenderMediaItems, album)}, td);
+        td.appendChild(dojo.doc.createTextNode(" > " + mediaItem.title));
+        
+        // Div to show MediaItem
+        var itemDiv = dojo.create('div', null, mediaItemDiv);
+        var table = dojo.create('table', null, itemDiv);
+        var tbody = dojo.create('tbody', null, table);
+        var tr = dojo.create('tr', null, tbody);
+        var td = dojo.create('td', null, tr);
+        dojo.create('img', {src: mediaItem.url}, td);
+        if (mediaItem.description) {
+            tr = dojo.create('tr', null, tbody);
+            td = dojo.create('td', null, tr);
+            td.appendChild(dojo.doc.createTextNode(mediaItem.description));
+        }
+        
+        divManager.showMediaItem();
+    }
+    
+    /*
+     * Popup to edit album.
+     */
+    function editAlbumPopup(album) {
+        console.log('editAlbumPopup: ' + JSON.stringify(album));
+        
+        var title = (album == null ? 'Create' : 'Edit') + ' Album';
+        var dialog = new dijit.Dialog({id: 'editAlbumPopup', title: title, onCancel: destroyDialog});
+        dojo.body().appendChild(dialog.domNode);
+        
+        var formDiv = dojo.create('div', {id: 'editAlbumFormDiv'});
+        var form = new dijit.form.Form({id: 'editAlbumForm'});
+        formDiv.appendChild(form.domNode);
+        var table = dojo.create('table', null, form.domNode);
+        var tbody = dojo.create('tbody', null, table);
+        var tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'Title', for: 'title'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.ValidationTextBox({
+                name: 'title',
+                value: album == null ? '' : album.title
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'Thumnail URL', for: 'thumbnail'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.ValidationTextBox({
+                name: 'thumbnail',
+                value: album == null ? '' : album.thumbnailUrl
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'Description', for: 'description'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.Textarea({
+                name: 'description',
+                value: album == null ? '' : album.description
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        var buttonTd = dojo.create('td', {colspan: '2', align: 'center'}, tr);
+        buttonTd.appendChild(new dijit.form.Button({
+                label: 'Save',
+                onClick: saveForm
+            }).domNode
+        );
+        buttonTd.appendChild(new dijit.form.Button({
+                label: 'Cancel',
+                onClick: destroyDialog
+            }).domNode
+        );
+        
+        dialog.set('content', formDiv);
+        dialog.show();
+        
+        function saveForm() {
+            console.log('saveForm');
+            var values = form.get('value');
+            var newAlbum = {
+                title: values.title,
+                thumbnailUrl: values.thumbnail,
+                description: values.description,
+                ownerId: viewer.id  // TODO: bug? Albums service should set this
+            };
+            if (album == null) {
+                social.createAlbum(viewer.id, newAlbum, function(response) {
+                    console.log('created album response: ' + JSON.stringify(response));
+                    renderAlbumsByUser(viewer.id);
+                });
+            } else {
+                social.updateAlbum(viewer.id, album.id, newAlbum, function(response) {
+                    console.log('updated album response: ' + JSON.stringify(response));
+                    renderAlbumsByUser(viewer.id);
+                });
+            }
+            destroyDialog();
+        }
+        
+        // Handles destroying the dialog popup
+        function destroyDialog() {
+            console.log('destroyDialog');
+            dialog.destroyRecursive(false);
+            dialog.destroyRendering(false);
+            dialog.destroy(false);
+        }
+    }
+    
+    /*
+     * Popup to edit MediaItem.
+     */
+    function editMediaItemPopup(album, mediaItem) {
+        console.log('editMediaItemPopup: ' + JSON.stringify(mediaItem));
+        
+        var albumId = mediaItem == null ? album.id : mediaItem.albumId;
+        var title = (mediaItem == null ? 'Create' : 'Edit') + ' MediaItem';
+        var dialog = new dijit.Dialog({id: 'editMediaItemPopup', title: title, onCancel: destroyDialog});
+        dojo.body().appendChild(dialog.domNode);
+        
+        // Form div
+        var formDiv = dojo.create('div', {id: 'editMediaItemFormDiv'});
+        var form = new dijit.form.Form({id: 'editMediaItemForm'});
+        formDiv.appendChild(form.domNode);
+        var table = dojo.create('table', null, form.domNode);
+        var tbody = dojo.create('tbody', null, table);
+        var tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'Title', for: 'title'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.ValidationTextBox({
+                name: 'title',
+                value: mediaItem == null ? '' : mediaItem.title
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'Description', for: 'description'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.Textarea({
+                name: 'description',
+                value: mediaItem == null ? '' : mediaItem.description
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'Type', for: 'type'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.ValidationTextBox({
+                name: 'type',
+                value: mediaItem == null ? '' : mediaItem.type
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'Thumnail URL', for: 'thumbnailUrl'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.ValidationTextBox({
+                name: 'thumbnailUrl',
+                value: mediaItem == null ? '' : mediaItem.thumbnailUrl
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        dojo.create('td', null, tr).appendChild(dojo.create('label', {innerHTML: 'URL', for: 'url'}));
+        dojo.create('td', null, tr).appendChild(
+            new dijit.form.ValidationTextBox({
+                name: 'url',
+                value: mediaItem == null ? '' : mediaItem.url
+            }).domNode
+        );
+        tr = dojo.create('tr', null, tbody);
+        var buttonTd = dojo.create('td', {colspan: '2', align: 'center'}, tr);
+        buttonTd.appendChild(new dijit.form.Button({
+                label: 'Save',
+                onClick: saveForm
+            }).domNode
+        );
+        buttonTd.appendChild(new dijit.form.Button({
+                label: 'Cancel',
+                onClick: destroyDialog
+            }).domNode
+        );
+        
+        // Textarea div for JSON
+        var textAreaDiv = dojo.create('div', {style: "width:100%; height:100%;", id: 'textAreaDiv'});
+        var textArea = new dijit.form.Textarea({value: JSON.stringify(mediaItem), rows: "20"});
+        textAreaDiv.appendChild(textArea.domNode);
+        
+        // Put divs together
+        var tabContainer = new dijit.layout.TabContainer({style: "width:400px; height:300px;"});
+        var formContentPane = new dijit.layout.ContentPane({title: "Form", content: formDiv});
+        tabContainer.addChild(formContentPane);
+        var textAreaContentPane = new dijit.layout.ContentPane({title: "JSON", content: textAreaDiv});
+        tabContainer.addChild(textAreaContentPane);
+        tabContainer.startup();
+        var dialogDiv = dojo.create('div', null);
+        dialogDiv.appendChild(tabContainer.domNode);
+        
+        dialog.set('content', dialogDiv);
+        dialog.show();
+        
+        function saveForm() {
+            console.log('saveForm mediaItem');
+            var values = form.get('value');
+            var newMediaItem = {
+                title: values.title,
+                description: values.description,
+                type: values.type,
+                thumbnailUrl: values.thumbnailUrl,
+                url: values.url
+            };
+            if (newMediaItem.type == null || newMediaItem.type == "") newMediaItem.type = "image";
+            if (mediaItem == null) {
+                social.createMediaItem(viewer.id, albumId, newMediaItem, function(response) {
+                    console.log('created MediaItem response: ' + JSON.stringify(response));
+                    social.getMediaItemsByAlbum(viewer.id, album.id, function(response) {
+                        renderMediaItems(album, response.list);
+                    });
+                });
+            } else {
+                social.updateMediaItem(viewer.id, albumId, mediaItem.id, newMediaItem, function(response) {
+                    console.log('updated MediaItem response: ' + JSON.stringify(response));
+                    social.getMediaItemsByAlbum(viewer.id, album.id, function(response) {
+                        renderMediaItems(album, response.list);
+                    });
+                });
+            }
+            destroyDialog();
+        }
+        
+        // Handles destroying the dialog popup
+        function destroyDialog() {
+            console.log('destroyDialog');
+            dialog.destroyRecursive(false);
+            dialog.destroyRendering(false);
+            dialog.destroy(false);
+        }
+    }
+    
+    /*
+     * Popup to confirm that the user wants to delete album.
+     */
+    function deleteAlbumPopup(album) {
+        console.log('deleteAlbumPopup');
+        if (confirm("Delete '" + album.title + "'?")) {
+            social.deleteAlbum(viewer.id, album.id, function(response) {
+                console.log('delete album response: ' + JSON.stringify(response));
+                renderAlbumsByUser(viewer.id);
+            });
+        }
+    }
+    
+    /*
+     * Popup to confirm user wants to delete MediaItem.
+     */
+    function deleteMediaItemPopup(album, mediaItem) {
+        console.log('deleteMediaItemPopup');
+        var albumId = mediaItem.albumId;
+        if (confirm("Delete '" + mediaItem.title + "'?")) {
+            social.deleteMediaItem(viewer.id, albumId, mediaItem.id, function(response) {
+                console.log('delete mediaItem response: ' + JSON.stringify(response));
+                social.getMediaItemsByAlbum(viewer.id, albumId, function(response) {
+                    renderMediaItems(album, response.list);
+                });
+            });
+        }
+    }
+}
\ No newline at end of file

Added: shindig/branches/2.0.x/content/samplecontainer/examples/media/Social.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/samplecontainer/examples/media/Social.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/samplecontainer/examples/media/Social.js (added)
+++ shindig/branches/2.0.x/content/samplecontainer/examples/media/Social.js Thu Oct  7 15:54:09 2010
@@ -0,0 +1,156 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+/*
+ * Defines high level functionality to interact with the OpenSocial API.
+ */
+function SocialWrapper() {
+
+    /*
+     * Retrieves the current viewer.
+     */
+    this.getViewer = function(callback) {
+        osapi.people.getViewer().execute(callback);
+    }
+    
+    /*
+     * Retrieves the current owner.
+     */
+    this.getOwner = function(callback) {
+        osapi.people.getOwner().execute(callback);
+    }
+    
+    //------------------------ ALBUMS ----------------------
+    /*
+     * Retrieves albums by ID(s).
+     */
+    this.getAlbumsById = function(userId, albumId, callback) {
+        var params = {userId: userId, albumId: albumId};
+        osapi.albums.get(params).execute(callback);
+    }
+     
+    /*
+     * Retrieves albums by user.
+     */
+    this.getAlbumsByUser = function(userId, callback) {
+        osapi.albums.get({userId: userId}).execute(callback);
+    }
+    
+    /*
+     * Retrieves albums by group.
+     */
+    this.getAlbumsByGroup = function(userId, groupId, callback) {
+        osapi.albums.get({userId: userId, groupId: groupId}).execute(callback);
+    }
+    
+    /*
+     * Creates an album for the given user.
+     */
+    this.createAlbum = function(userId, album, callback) {
+        var params = {
+            userId: userId,
+            album: album
+        };
+        osapi.albums.create(params).execute(callback);
+    }
+    
+    /*
+     * Updates an album by ID.
+     */
+    this.updateAlbum = function(userId, albumId, album, callback) {
+        var params = {
+            userId: userId,
+            albumId: albumId,
+            album: album
+        };
+        osapi.albums.update(params).execute(callback);
+    }
+    
+    /*
+     * Deletes an album by ID.
+     */
+    this.deleteAlbum = function(userId, albumId, callback) {
+        var params = {userId: userId, albumId: albumId};
+        osapi.albums.delete(params).execute(callback);
+    }
+    
+    //------------------------------- MEDIAITEMS ----------------------------
+    /*
+     * Creates a MediaItem.
+     */
+    this.createMediaItem = function(userId, albumId, mediaItem, callback) {
+        var params = {
+            userId: userId,
+            albumId: albumId,
+            mediaItem: mediaItem
+        };
+        osapi.mediaItems.create(params).execute(callback);
+    }
+    
+    /*
+     * Updates a MediaItem by ID.
+     */
+    this.updateMediaItem = function(userId, albumId, mediaItemId, mediaItem, callback) {
+        var params = {
+            userId: userId,
+            albumId: albumId,
+            mediaItemId: mediaItemId,
+            mediaItem: mediaItem
+        };
+        console.log("PARAMS: " + JSON.stringify(params));
+        osapi.mediaItems.update(params).execute(callback);
+    }
+    
+    /*
+     * Retrieves MediaItems by ID(s).
+     */
+    this.getMediaItemsById = function(userId, albumId, mediaItemId, callback) {
+        var params = {
+            userId: userId,
+            albumId: albumId,
+            mediaItemId: mediaItemId
+        };
+        osapi.mediaItems.get(params).execute(callback);
+    }
+    
+    /*
+     * Retrieves MediaItems by album.
+     */
+    this.getMediaItemsByAlbum = function(userId, albumId, callback) {
+        osapi.mediaItems.get({userId: userId, albumId: albumId}).execute(callback);
+    }
+     
+    /*
+     * Retrieves MediaItems by user and group.
+     */
+    this.getMediaItemsByUser = function(userId, groupId, callback) {
+        osapi.mediaItems.get({userId: userId, groupId: groupId}).execute(callback);
+    }
+     
+    /*
+     * Deletes a MediaItem by ID.
+     */
+    this.deleteMediaItem = function(userId, albumId, mediaItemId, callback) {
+        var params = {
+            userId: userId,
+            albumId: albumId,
+            mediaItemId: mediaItemId
+        };
+        osapi.mediaItems.delete(params).execute(callback);
+    }
+}
\ No newline at end of file

Added: shindig/branches/2.0.x/content/samplecontainer/examples/media/styles.css
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/content/samplecontainer/examples/media/styles.css?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/content/samplecontainer/examples/media/styles.css (added)
+++ shindig/branches/2.0.x/content/samplecontainer/examples/media/styles.css Thu Oct  7 15:54:09 2010
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+/* ============ ROUND 2 ============== */
+
+td.albumsTitle {
+    font-family:'comic sans ms';
+    width: 100%;
+    font-size:24px;
+}
+
+td.albumListThumbnail {
+    width: 10%;
+    height: 75px;
+}
+
+td.albumListRight {
+    width: 90%;
+}
+
+td.actionLinks {
+    width:100%;
+    font-size:12px;
+}
+
+td.albumListTitle {
+    font-size:24px;
+    white-space:nowrap;
+    font-family:'comic sans ms';
+}
+
+td.albumListDescription {
+    font-family:'comic sans ms';
+}
+
+.albumsTable {
+    width:100%;
+    border-style:solid;
+    background-color:#b0c4de;
+}
+
+td.mediaItemThumbnail {
+    height:100%;
+}
+
+td.mediaItemBox {
+    width: 150px;
+    height: 100px;
+}
+
+.mediaItemControls {
+
+}
+
+
+/* ============ ROUND 1 ============== */
+.temp1 {background-color:#6495ed;}
+.temp2 {background-color:#e0ffff;}
+.temp3 {
+    background-color:#b0c4de;
+    background-image:url('img_flwr.png');
+    background-repeat:no-repeat;
+    background-position:top right;
+}
+
+td2 {
+    border-style:solid;
+}
+
+td.albumLeft {
+    width:10%;
+}
+
+td.albumRight {
+    width: 90%;
+}
+
+
+
+td.albumEdit {
+    width:100%;
+    text-align:right;
+    vertical-align:middle;
+    font-size:12px;
+}
+
+
+
+.albumTitleStyle {
+    color: blue;
+    
+}
+.albumElement {
+    background-color:#b0c4de;
+}

Modified: shindig/branches/2.0.x/etc/checkstyle/checkstyle.xml
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/etc/checkstyle/checkstyle.xml?rev=1005506&r1=1005505&r2=1005506&view=diff
==============================================================================
--- shindig/branches/2.0.x/etc/checkstyle/checkstyle.xml (original)
+++ shindig/branches/2.0.x/etc/checkstyle/checkstyle.xml Thu Oct  7 15:54:09 2010
@@ -52,6 +52,10 @@ under the License.
 
 <module name="Checker">
 
+    <module name="LineLength">
+      <property name="max" value="100"/>
+    </module>
+
     <!-- Checks that a package.html file exists for each package.     -->
     <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->
     <module name="PackageHtml">

Added: shindig/branches/2.0.x/etc/run-gjslint
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/etc/run-gjslint?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/etc/run-gjslint (added)
+++ shindig/branches/2.0.x/etc/run-gjslint Thu Oct  7 15:54:09 2010
@@ -0,0 +1,2 @@
+#!/bin/sh
+gjslint --strict --unix_mode --exclude_directories features/src/main/javascript/features/swfobject,features/src/main/javascript/features/i18n --recurse features/src/main/javascript/features 

Propchange: shindig/branches/2.0.x/etc/run-gjslint
------------------------------------------------------------------------------
    svn:executable = *

Modified: shindig/branches/2.0.x/etc/set_svn_properties.sh
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/etc/set_svn_properties.sh?rev=1005506&r1=1005505&r2=1005506&view=diff
==============================================================================
--- shindig/branches/2.0.x/etc/set_svn_properties.sh (original)
+++ shindig/branches/2.0.x/etc/set_svn_properties.sh Thu Oct  7 15:54:09 2010
@@ -33,6 +33,7 @@
 #svn propset svn:ignore -F etc/svn-ignores java/social-api
 #svn propset svn:ignore -F etc/svn-ignores java/server
 #svn propset svn:ignore -F etc/svn-ignores java/samples
+#svn propset svn:ignore -F etc/svn-ignores php
 
 # Language files
 find . -name "*.java" | grep -v '.svn' | xargs -n 1 svn propset svn:eol-style native

Modified: shindig/branches/2.0.x/extras/src/main/java/org/apache/shindig/extras/as/opensocial/service/ActivityStreamsHandler.java
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/extras/src/main/java/org/apache/shindig/extras/as/opensocial/service/ActivityStreamsHandler.java?rev=1005506&r1=1005505&r2=1005506&view=diff
==============================================================================
--- shindig/branches/2.0.x/extras/src/main/java/org/apache/shindig/extras/as/opensocial/service/ActivityStreamsHandler.java (original)
+++ shindig/branches/2.0.x/extras/src/main/java/org/apache/shindig/extras/as/opensocial/service/ActivityStreamsHandler.java Thu Oct  7 15:54:09 2010
@@ -42,7 +42,7 @@ import com.google.inject.Inject;
  * <p>ActivityStreamsHandler class.</p>
  *
  */
-@Service(name = "activitystreams", path="/{userId}+/{groupId}/{appId}/{activitystream}/{activityEntryId}+")
+@Service(name = "activitystreams", path="/{userId}+/{groupId}/{appId}/{activityEntryId}+")
 public class ActivityStreamsHandler {
 
   private final ActivityStreamService service;

Modified: shindig/branches/2.0.x/extras/src/main/javascript/features-extras/features.txt
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/extras/src/main/javascript/features-extras/features.txt?rev=1005506&r1=1005505&r2=1005506&view=diff
==============================================================================
--- shindig/branches/2.0.x/extras/src/main/javascript/features-extras/features.txt (original)
+++ shindig/branches/2.0.x/extras/src/main/javascript/features-extras/features.txt Thu Oct  7 15:54:09 2010
@@ -20,4 +20,4 @@ features-extras/org.jquery.core-1.4.2/fe
 features-extras/wave/feature.xml
 features-extras/opensocial-payment/feature.xml
 features-extras/pubsub-2/feature.xml
-features-extras/org.openajax.hub-2.0.4/feature.xml
+features-extras/org.openajax.hub-2.0.5/feature.xml

Added: shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/OpenAjax-mashup.js
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/OpenAjax-mashup.js?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/OpenAjax-mashup.js (added)
+++ shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/OpenAjax-mashup.js Thu Oct  7 15:54:09 2010
@@ -0,0 +1,1357 @@
+/*******************************************************************************
+ * OpenAjax-mashup.js
+ *
+ * Reference implementation of the OpenAjax Hub, as specified by OpenAjax Alliance.
+ * Specification is under development at: 
+ *
+ *   http://www.openajax.org/member/wiki/OpenAjax_Hub_Specification
+ *
+ * Copyright 2006-2009 OpenAjax Alliance
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
+ * use this file except in compliance with the License. You may obtain a copy 
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0 . Unless 
+ * required by applicable law or agreed to in writing, software distributed 
+ * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the 
+ * specific language governing permissions and limitations under the License.
+ *
+ ******************************************************************************/
+
+var OpenAjax = OpenAjax || {};
+
+if ( !OpenAjax.hub ) {  // prevent re-definition of the OpenAjax.hub object
+
+OpenAjax.hub = function() {
+    var libs = {};
+    var ooh = "org.openajax.hub.";
+
+    return /** @scope OpenAjax.hub */ {
+        implementer: "http://openajax.org",
+        implVersion: "2.0.5",
+        specVersion: "2.0",
+        implExtraData: {},
+        libraries: libs,
+    
+        registerLibrary: function(prefix, nsURL, version, extra) {
+            libs[prefix] = {
+                prefix: prefix,
+                namespaceURI: nsURL,
+                version: version,
+                extraData: extra 
+            };
+            this.publish(ooh+"registerLibrary", libs[prefix]);
+        },
+        
+        unregisterLibrary: function(prefix) {
+            this.publish(ooh+"unregisterLibrary", libs[prefix]);
+            delete libs[prefix];
+        }
+    };
+}();
+
+/**
+ * Error
+ * 
+ * Standard Error names used when the standard functions need to throw Errors.
+ */
+OpenAjax.hub.Error = {
+    // Either a required argument is missing or an invalid argument was provided
+    BadParameters: "OpenAjax.hub.Error.BadParameters",
+    // The specified hub has been disconnected and cannot perform the requested
+    // operation:
+    Disconnected: "OpenAjax.hub.Error.Disconnected",
+    // Container with specified ID already exists:
+    Duplicate: "OpenAjax.hub.Error.Duplicate",
+    // The specified ManagedHub has no such Container (or it has been removed)
+    NoContainer: "OpenAjax.hub.Error.NoContainer",
+    // The specified ManagedHub or Container has no such subscription
+    NoSubscription: "OpenAjax.hub.Error.NoSubscription",
+    // Permission denied by manager's security policy
+    NotAllowed: "OpenAjax.hub.Error.NotAllowed",
+    // Wrong communications protocol identifier provided by Container or HubClient
+    WrongProtocol: "OpenAjax.hub.Error.WrongProtocol",
+    // A 'tunnelURI' param was specified, but current browser does not support security features
+    IncompatBrowser: "OpenAjax.hub.Error.IncompatBrowser"
+};
+
+/**
+ * SecurityAlert
+ * 
+ * Standard codes used when attempted security violations are detected. Unlike
+ * Errors, these codes are not thrown as exceptions but rather passed into the 
+ * SecurityAlertHandler function registered with the Hub instance.
+ */
+OpenAjax.hub.SecurityAlert = {
+    // Container did not load (possible frame phishing attack)
+    LoadTimeout: "OpenAjax.hub.SecurityAlert.LoadTimeout",
+    // Hub suspects a frame phishing attack against the specified container
+    FramePhish: "OpenAjax.hub.SecurityAlert.FramePhish",
+    // Hub detected a message forgery that purports to come to a specified
+    // container
+    ForgedMsg: "OpenAjax.hub.SecurityAlert.ForgedMsg"
+};
+
+/**
+ * Debugging Help
+ *
+ * OpenAjax.hub.enableDebug
+ *
+ *      If OpenAjax.hub.enableDebug is set to true, then the "debugger" keyword
+ *      will get hit whenever a user callback throws an exception, thereby
+ *      bringing up the JavaScript debugger.
+ */
+OpenAjax.hub._debugger = function() {
+//    if ( OpenAjax.hub.enableDebug ) debugger; // REMOVE ON BUILD
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Hub interface
+ * 
+ * Hub is implemented on the manager side by ManagedHub and on the client side
+ * by ClientHub.
+ */
+//OpenAjax.hub.Hub = function() {}
+
+/**
+ * Subscribe to a topic.
+ *
+ * @param {String} topic
+ *     A valid topic string. MAY include wildcards.
+ * @param {Function} onData   
+ *     Callback function that is invoked whenever an event is 
+ *     published on the topic
+ * @param {Object} [scope]
+ *     When onData callback or onComplete callback is invoked,
+ *     the JavaScript "this" keyword refers to this scope object.
+ *     If no scope is provided, default is window.
+ * @param {Function} [onComplete]
+ *     Invoked to tell the client application whether the 
+ *     subscribe operation succeeded or failed. 
+ * @param {*} [subscriberData]
+ *     Client application provides this data, which is handed
+ *     back to the client application in the subscriberData
+ *     parameter of the onData callback function.
+ * 
+ * @returns subscriptionID
+ *     Identifier representing the subscription. This identifier is an 
+ *     arbitrary ID string that is unique within this Hub instance
+ * @type {String}
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.BadParameters} if the topic is invalid (e.g. contains an empty token)
+ */
+//OpenAjax.hub.Hub.prototype.subscribe = function( topic, onData, scope, onComplete, subscriberData ) {}
+
+/**
+ * Publish an event on a topic
+ *
+ * @param {String} topic
+ *     A valid topic string. MUST NOT include wildcards.
+ * @param {*} data
+ *     Valid publishable data. To be portable across different
+ *     Container implementations, this value SHOULD be serializable
+ *     as JSON.
+ *     
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.BadParameters} if the topic cannot be published (e.g. contains 
+ *     wildcards or empty tokens) or if the data cannot be published (e.g. cannot be serialized as JSON)
+ */
+//OpenAjax.hub.Hub.prototype.publish = function( topic, data ) {}
+
+/**
+ * Unsubscribe from a subscription
+ *
+ * @param {String} subscriptionID
+ *     A subscriptionID returned by Hub.subscribe()
+ * @param {Function} [onComplete]
+ *     Callback function invoked when unsubscribe completes
+ * @param {Object} [scope]
+ *     When onComplete callback function is invoked, the JavaScript "this"
+ *     keyword refers to this scope object.
+ *     If no scope is provided, default is window.
+ *     
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.NoSubscription} if no such subscription is found
+ */
+//OpenAjax.hub.Hub.prototype.unsubscribe = function( subscriptionID, onComplete, scope ) {}
+
+/**
+ * Return true if this Hub instance is in the Connected state.
+ * Else returns false.
+ * 
+ * This function can be called even if the Hub is not in a CONNECTED state.
+ * 
+ * @returns Boolean
+ * @type {Boolean}
+ */
+//OpenAjax.hub.Hub.prototype.isConnected = function() {}
+
+/**
+ * Returns the scope associated with this Hub instance and which will be used
+ * with callback functions.
+ * 
+ * This function can be called even if the Hub is not in a CONNECTED state.
+ * 
+ * @returns scope object
+ * @type {Object}
+ */
+//OpenAjax.hub.Hub.prototype.getScope = function() {}
+
+/**
+ * Returns the subscriberData parameter that was provided when 
+ * Hub.subscribe was called.
+ *
+ * @param {String} subscriptionID
+ *     The subscriberID of a subscription
+ * 
+ * @returns subscriberData
+ * @type {*}
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
+ */
+//OpenAjax.hub.Hub.prototype.getSubscriberData = function(subscriptionID) {}
+
+/**
+ * Returns the scope associated with a specified subscription.  This scope will
+ * be used when invoking the 'onData' callback supplied to Hub.subscribe().
+ *
+ * @param {String} subscriberID
+ *     The subscriberID of a subscription
+ * 
+ * @returns scope
+ * @type {*}
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
+ */
+//OpenAjax.hub.Hub.prototype.getSubscriberScope = function(subscriberID) {}
+
+/**
+ * Returns the params object associated with this Hub instance.
+ *
+ * @returns params
+ *     The params object associated with this Hub instance
+ * @type {Object}
+ */
+//OpenAjax.hub.Hub.prototype.getParameters = function() {}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * HubClient interface 
+ * 
+ * Extends Hub interface.
+ * 
+ * A HubClient implementation is typically specific to a particular 
+ * implementation of Container.
+ */
+
+/**
+ * Create a new HubClient. All HubClient constructors MUST have this 
+ * signature.
+ * @constructor
+ * 
+ * @param {Object} params 
+ *    Parameters used to instantiate the HubClient.
+ *    Once the constructor is called, the params object belongs to the
+ *    HubClient. The caller MUST not modify it.
+ *    Implementations of HubClient may specify additional properties
+ *    for the params object, besides those identified below. 
+ * 
+ * @param {Function} params.HubClient.onSecurityAlert
+ *     Called when an attempted security breach is thwarted
+ * @param {Object} [params.HubClient.scope]
+ *     Whenever one of the HubClient's callback functions is called,
+ *     references to "this" in the callback will refer to the scope object.
+ *     If not provided, the default is window.
+ * @param {Function} [params.HubClient.log]
+ *     Optional logger function. Would be used to log to console.log or
+ *     equivalent. 
+ *     
+ * @throws {OpenAjax.hub.Error.BadParameters} if any of the required
+ *     parameters is missing, or if a parameter value is invalid in 
+ *     some way.
+ */
+//OpenAjax.hub.HubClient = function( params ) {}
+
+/**
+ * Requests a connection to the ManagedHub, via the Container
+ * associated with this HubClient.
+ * 
+ * If the Container accepts the connection request, the HubClient's 
+ * state is set to CONNECTED and the HubClient invokes the 
+ * onComplete callback function.
+ * 
+ * If the Container refuses the connection request, the HubClient
+ * invokes the onComplete callback function with an error code. 
+ * The error code might, for example, indicate that the Container 
+ * is being destroyed.
+ * 
+ * In most implementations, this function operates asynchronously, 
+ * so the onComplete callback function is the only reliable way to
+ * determine when this function completes and whether it has succeeded
+ * or failed.
+ * 
+ * A client application may call HubClient.disconnect and then call
+ * HubClient.connect.
+ * 
+ * @param {Function} [onComplete]
+ *     Callback function to call when this operation completes.
+ * @param {Object} [scope]  
+ *     When the onComplete function is invoked, the JavaScript "this"
+ *     keyword refers to this scope object.
+ *     If no scope is provided, default is window.
+ *
+ * @throws {OpenAjax.hub.Error.Duplicate} if the HubClient is already connected
+ */
+//OpenAjax.hub.HubClient.prototype.connect = function( onComplete, scope ) {}
+
+/**
+ * Disconnect from the ManagedHub
+ * 
+ * Disconnect immediately:
+ * 
+ * 1. Sets the HubClient's state to DISCONNECTED.
+ * 2. Causes the HubClient to send a Disconnect request to the 
+ * 		associated Container. 
+ * 3. Ensures that the client application will receive no more
+ * 		onData or onComplete callbacks associated with this 
+ * 		connection, except for the disconnect function's own
+ * 		onComplete callback.
+ * 4. Automatically destroys all of the HubClient's subscriptions.
+ *
+ * In most implementations, this function operates asynchronously, 
+ * so the onComplete callback function is the only reliable way to
+ * determine when this function completes and whether it has succeeded
+ * or failed.
+ * 
+ * A client application is allowed to call HubClient.disconnect and 
+ * then call HubClient.connect.
+ * 	
+ * @param {Function} [onComplete]
+ *     Callback function to call when this operation completes.
+ * @param {Object} [scope]  
+ *     When the onComplete function is invoked, the JavaScript "this"
+ *     keyword refers to the scope object.
+ *     If no scope is provided, default is window.
+ *
+ * @throws {OpenAjax.hub.Error.Disconnected} if the HubClient is already
+ *     disconnected
+ */
+//OpenAjax.hub.HubClient.prototype.disconnect = function( onComplete, scope ) {}
+
+/**
+ * If DISCONNECTED: Returns null
+ * If CONNECTED: Returns the origin associated with the window containing the
+ * Container associated with this HubClient instance. The origin has the format
+ *  
+ * [protocol]://[host]
+ * 
+ * where:
+ * 
+ * [protocol] is "http" or "https"
+ * [host] is the hostname of the partner page.
+ * 
+ * @returns Partner's origin
+ * @type {String}
+ */
+//OpenAjax.hub.HubClient.prototype.getPartnerOrigin = function() {}
+
+/**
+ * Returns the client ID of this HubClient
+ *
+ * @returns clientID
+ * @type {String}
+ */
+//OpenAjax.hub.HubClient.prototype.getClientID = function() {}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * OpenAjax.hub.ManagedHub
+ *
+ * Managed hub API for the manager application and for Containers. 
+ * 
+ * Implements OpenAjax.hub.Hub.
+ */
+
+/**
+ * Create a new ManagedHub instance
+ * @constructor
+ *     
+ * This constructor automatically sets the ManagedHub's state to
+ * CONNECTED.
+ * 
+ * @param {Object} params
+ *     Parameters used to instantiate the ManagedHub.
+ *     Once the constructor is called, the params object belongs exclusively to
+ *     the ManagedHub. The caller MUST not modify it.
+ *     
+ * The params object may contain the following properties:
+ * 
+ * @param {Function} params.onPublish
+ *     Callback function that is invoked whenever a 
+ *     data value published by a Container is about
+ *     to be delivered to some (possibly the same) Container.
+ *     This callback function implements a security policy;
+ *     it returns true if the delivery of the data is
+ *     permitted and false if permission is denied.
+ * @param {Function} params.onSubscribe
+ *     Called whenever a Container tries to subscribe
+ *     on behalf of its client.
+ *     This callback function implements a security policy;
+ *     it returns true if the subscription is permitted 
+ *     and false if permission is denied.
+ * @param {Function} [params.onUnsubscribe]
+ *     Called whenever a Container unsubscribes on behalf of its client. 
+ *     Unlike the other callbacks, onUnsubscribe is intended only for 
+ *     informative purposes, and is not used to implement a security
+ *     policy.
+ * @param {Object} [params.scope]
+ *     Whenever one of the ManagedHub's callback functions is called,
+ *     references to the JavaScript "this" keyword in the callback 
+ *     function refer to this scope object
+ *     If no scope is provided, default is window.
+ * @param {Function} [params.log]  Optional logger function. Would
+ *     be used to log to console.log or equivalent.
+ * 
+ * @throws {OpenAjax.hub.Error.BadParameters} if any of the required
+ *     parameters are missing
+ */
+OpenAjax.hub.ManagedHub = function( params )
+{
+    if ( ! params || ! params.onPublish || ! params.onSubscribe )
+        throw new Error( OpenAjax.hub.Error.BadParameters );
+    
+    this._p = params;
+    this._onUnsubscribe = params.onUnsubscribe ? params.onUnsubscribe : null;
+    this._scope = params.scope || window;
+
+    if ( params.log ) {
+        var that = this;
+        this._log = function( msg ) {
+            try {
+                params.log.call( that._scope, "ManagedHub: " + msg );
+            } catch( e ) {
+                OpenAjax.hub._debugger();
+            }
+        };
+    } else {
+        this._log = function() {};
+    }
+
+    this._subscriptions = { c:{}, s:null };
+    this._containers = {};
+
+    // Sequence # used to create IDs that are unique within this hub
+    this._seq = 0;
+
+    this._active = true;
+    
+    this._isPublishing = false;
+    this._pubQ = [];
+}
+
+/**
+ * Subscribe to a topic on behalf of a Container. Called only by 
+ * Container implementations, NOT by manager applications.
+ * 
+ * This function:
+ * 1. Checks with the ManagedHub's onSubscribe security policy
+ *    to determine whether this Container is allowed to subscribe 
+ *    to this topic.
+ * 2. If the subscribe operation is permitted, subscribes to the
+ *    topic and returns the ManagedHub's subscription ID for this
+ *    subscription. 
+ * 3. If the subscribe operation is not permitted, throws
+ *    OpenAjax.hub.Error.NotAllowed.
+ * 
+ * When data is published on the topic, the ManagedHub's 
+ * onPublish security policy will be invoked to ensure that
+ * this Container is permitted to receive the published data.
+ * If the Container is allowed to receive the data, then the
+ * Container's sendToClient function will be invoked.
+ * 
+ * When a Container needs to create a subscription on behalf of
+ * its client, the Container MUST use this function to create
+ * the subscription.
+ * 
+ * @param {OpenAjax.hub.Container} container  
+ *     A Container
+ * @param {String} topic 
+ *     A valid topic
+ * @param {String} containerSubID  
+ *     Arbitrary string ID that the Container uses to 
+ *     represent the subscription. Must be unique within the 
+ *     context of the Container
+ *
+ * @returns managerSubID  
+ *     Arbitrary string ID that this ManagedHub uses to 
+ *     represent the subscription. Will be unique within the 
+ *     context of this ManagedHub
+ * @type {String}
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this.isConnected() returns false
+ * @throws {OpenAjax.hub.Error.NotAllowed} if subscription request is denied by the onSubscribe security policy
+ * @throws {OpenAjax.hub.Error.BadParameters} if one of the parameters, e.g. the topic, is invalid
+ */
+OpenAjax.hub.ManagedHub.prototype.subscribeForClient = function( container, topic, containerSubID )
+{
+    this._assertConn();
+    // check subscribe permission
+    if ( this._invokeOnSubscribe( topic, container ) ) {
+        // return ManagedHub's subscriptionID for this subscription
+        return this._subscribe( topic, this._sendToClient, this, { c: container, sid: containerSubID } );
+    }
+    throw new Error(OpenAjax.hub.Error.NotAllowed);
+}
+
+/**
+ * Unsubscribe from a subscription on behalf of a Container. Called only by 
+ * Container implementations, NOT by manager application code.
+ * 
+ * This function:
+ * 1. Destroys the specified subscription
+ * 2. Calls the ManagedHub's onUnsubscribe callback function
+ * 
+ * This function can be called even if the ManagedHub is not in a CONNECTED state.
+ * 
+ * @param {OpenAjax.hub.Container} container  
+ *    container instance that is unsubscribing
+ * @param {String} managerSubID  
+ *    opaque ID of a subscription, returned by previous call to subscribeForClient()
+ * 
+ * @throws {OpenAjax.hub.Error.NoSubscription} if subscriptionID does not refer to a valid subscription
+ */
+OpenAjax.hub.ManagedHub.prototype.unsubscribeForClient = function( container, managerSubID )
+{
+    this._unsubscribe( managerSubID );
+    this._invokeOnUnsubscribe( container, managerSubID );
+}
+  
+/**
+ * Publish data on a topic on behalf of a Container. Called only by 
+ * Container implementations, NOT by manager application code.
+ *
+ * @param {OpenAjax.hub.Container} container
+ *      Container on whose behalf data should be published
+ * @param {String} topic
+ *      Valid topic string. Must NOT contain wildcards.
+ * @param {*} data
+ *      Valid publishable data. To be portable across different
+ *      Container implementations, this value SHOULD be serializable
+ *      as JSON.
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this.isConnected() returns false
+ * @throws {OpenAjax.hub.Error.BadParameters} if one of the parameters, e.g. the topic, is invalid
+ */
+OpenAjax.hub.ManagedHub.prototype.publishForClient = function( container, topic, data )
+{
+    this._assertConn();
+    this._publish( topic, data, container );
+}
+
+/**
+ * Destroy this ManagedHub
+ * 
+ * 1. Sets state to DISCONNECTED. All subsequent attempts to add containers,
+ *  publish or subscribe will throw the Disconnected error. We will
+ *  continue to allow "cleanup" operations such as removeContainer
+ *  and unsubscribe, as well as read-only operations such as 
+ *  isConnected
+ * 2. Remove all Containers associated with this ManagedHub
+ */
+OpenAjax.hub.ManagedHub.prototype.disconnect = function()
+{
+    this._active = false;
+    for (var c in this._containers) {
+        this.removeContainer( this._containers[c] );
+    }
+}
+
+/**
+ * Get a container belonging to this ManagedHub by its clientID, or null
+ * if this ManagedHub has no such container
+ * 
+ * This function can be called even if the ManagedHub is not in a CONNECTED state.
+ * 
+ * @param {String} containerId
+ *      Arbitrary string ID associated with the container
+ *
+ * @returns container associated with given ID
+ * @type {OpenAjax.hub.Container}
+ */
+OpenAjax.hub.ManagedHub.prototype.getContainer = function( containerId ) 
+{
+    var container = this._containers[containerId];
+    return container ? container : null;
+}
+
+/**
+ * Returns an array listing all containers belonging to this ManagedHub.
+ * The order of the Containers in this array is arbitrary.
+ * 
+ * This function can be called even if the ManagedHub is not in a CONNECTED state.
+ * 
+ * @returns container array
+ * @type {OpenAjax.hub.Container[]}
+ */
+OpenAjax.hub.ManagedHub.prototype.listContainers = function() 
+{
+    var res = [];
+    for (var c in this._containers) { 
+        res.push(this._containers[c]);
+    }
+    return res;
+}
+
+/**
+ * Add a container to this ManagedHub.
+ *
+ * This function should only be called by a Container constructor.
+ * 
+ * @param {OpenAjax.hub.Container} container
+ *      A Container to be added to this ManagedHub
+ * 
+ * @throws {OpenAjax.hub.Error.Duplicate} if there is already a Container
+ *      in this ManagedHub whose clientId is the same as that of container
+ * @throws {OpenAjax.hub.Error.Disconnected} if this.isConnected() returns false
+ */
+OpenAjax.hub.ManagedHub.prototype.addContainer = function( container ) 
+{ 
+    this._assertConn();
+    var containerId = container.getClientID();
+    if ( this._containers[containerId] ) {
+        throw new Error(OpenAjax.hub.Error.Duplicate);
+    }
+    this._containers[containerId] = container;
+}
+
+/**
+ * Remove a container from this ManagedHub immediately
+ * 
+ * This function can be called even if the ManagedHub is not in a CONNECTED state.
+ * 
+ * @param {OpenAjax.hub.Container} container  
+ *      A Container to be removed from this ManagedHub
+ *  
+ * @throws {OpenAjax.hub.Error.NoContainer}  if no such container is found
+ */
+OpenAjax.hub.ManagedHub.prototype.removeContainer = function( container )
+{
+    var containerId = container.getClientID();
+    if ( ! this._containers[ containerId ] ) {
+        throw new Error(OpenAjax.hub.Error.NoContainer);
+    }
+    container.remove();
+    delete this._containers[ containerId ];
+}
+
+    /*** OpenAjax.hub.Hub interface implementation ***/
+
+/**
+ * Subscribe to a topic.
+ * 
+ * This implementation of Hub.subscribe is synchronous. When subscribe 
+ * is called:
+ * 
+ * 1. The ManagedHub's onSubscribe callback is invoked. The 
+ * 		container parameter is null, because the manager application, 
+ * 		rather than a container, is subscribing.
+ * 2. If onSubscribe returns true, then the subscription is created.
+ * 3. The onComplete callback is invoked.
+ * 4. Then this function returns.
+ * 
+ * @param {String} topic
+ *     A valid topic string. MAY include wildcards.
+ * @param {Function} onData   
+ *     Callback function that is invoked whenever an event is 
+ *     published on the topic
+ * @param {Object} [scope]
+ *     When onData callback or onComplete callback is invoked,
+ *     the JavaScript "this" keyword refers to this scope object.
+ *     If no scope is provided, default is window.
+ * @param {Function} [onComplete]
+ *     Invoked to tell the client application whether the 
+ *     subscribe operation succeeded or failed. 
+ * @param {*} [subscriberData]
+ *     Client application provides this data, which is handed
+ *     back to the client application in the subscriberData
+ *     parameter of the onData and onComplete callback functions.
+ * 
+ * @returns subscriptionID
+ *     Identifier representing the subscription. This identifier is an 
+ *     arbitrary ID string that is unique within this Hub instance
+ * @type {String}
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.BadParameters} if the topic is invalid (e.g. contains an empty token)
+ */
+OpenAjax.hub.ManagedHub.prototype.subscribe = function( topic, onData, scope, onComplete, subscriberData ) 
+{
+    this._assertConn();
+    this._assertSubTopic(topic);
+    if ( ! onData ) {
+        throw new Error( OpenAjax.hub.Error.BadParameters );
+    }
+    
+    scope = scope || window;
+    
+    // check subscribe permission
+    if ( ! this._invokeOnSubscribe( topic, null ) ) {
+        this._invokeOnComplete( onComplete, scope, null, false, OpenAjax.hub.Error.NotAllowed );
+        return;
+    }
+    
+    // on publish event, check publish permissions
+    var that = this;
+    function publishCB( topic, data, sd, pcont ) {
+        if ( that._invokeOnPublish( topic, data, pcont, null ) ) {
+            try {
+                onData.call( scope, topic, data, subscriberData );
+            } catch( e ) {
+                OpenAjax.hub._debugger();
+                that._log( "caught error from onData callback to Hub.subscribe(): " + e.message );
+            }
+        }
+    }
+    var subID = this._subscribe( topic, publishCB, scope, subscriberData );
+    this._invokeOnComplete( onComplete, scope, subID, true );
+    return subID;
+}
+
+/**
+ * Publish an event on a topic
+ *
+ * This implementation of Hub.publish is synchronous. When publish 
+ * is called:
+ * 
+ * 1. The target subscriptions are identified.
+ * 2. For each target subscription, the ManagedHub's onPublish
+ * 		callback is invoked. Data is only delivered to a target
+ * 		subscription if the onPublish callback returns true.
+ * 		The pcont parameter of the onPublish callback is null.
+ *      This is because the ManagedHub, rather than a container,
+ *      is publishing the data.
+ * 
+ * @param {String} topic
+ *     A valid topic string. MUST NOT include wildcards.
+ * @param {*} data
+ *     Valid publishable data. To be portable across different
+ *     Container implementations, this value SHOULD be serializable
+ *     as JSON.
+ *     
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.BadParameters} if the topic cannot be published (e.g. contains 
+ *     wildcards or empty tokens) or if the data cannot be published (e.g. cannot be serialized as JSON)
+ */
+OpenAjax.hub.ManagedHub.prototype.publish = function( topic, data ) 
+{
+    this._assertConn();
+    this._assertPubTopic(topic);
+    this._publish( topic, data, null );
+}
+
+/**
+ * Unsubscribe from a subscription
+ * 
+ * This implementation of Hub.unsubscribe is synchronous. When unsubscribe 
+ * is called:
+ * 
+ * 1. The subscription is destroyed.
+ * 2. The ManagedHub's onUnsubscribe callback is invoked, if there is one.
+ * 3. The onComplete callback is invoked.
+ * 4. Then this function returns.
+ * 
+ * @param {String} subscriptionID
+ *     A subscriptionID returned by Hub.subscribe()
+ * @param {Function} [onComplete]
+ *     Callback function invoked when unsubscribe completes
+ * @param {Object} [scope]
+ *     When onComplete callback function is invoked, the JavaScript "this"
+ *     keyword refers to this scope object.
+ *     If no scope is provided, default is window.
+ *     
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.NoSubscription} if no such subscription is found
+ */
+OpenAjax.hub.ManagedHub.prototype.unsubscribe = function( subscriptionID, onComplete, scope )
+{
+    this._assertConn();
+    if ( ! subscriptionID ) {
+        throw new Error( OpenAjax.hub.Error.BadParameters );
+    }
+    this._unsubscribe( subscriptionID );
+    this._invokeOnUnsubscribe( null, subscriptionID );
+    this._invokeOnComplete( onComplete, scope, subscriptionID, true );
+}
+
+/**
+ * Returns true if disconnect() has NOT been called on this ManagedHub, 
+ * else returns false
+ * 
+ * @returns Boolean
+ * @type {Boolean}
+ */
+OpenAjax.hub.ManagedHub.prototype.isConnected = function()
+{
+    return this._active;
+}
+
+/**
+* Returns the scope associated with this Hub instance and which will be used
+* with callback functions.
+* 
+* This function can be called even if the Hub is not in a CONNECTED state.
+* 
+* @returns scope object
+* @type {Object}
+ */
+OpenAjax.hub.ManagedHub.prototype.getScope = function()
+{
+    return this._scope;
+}
+
+/**
+ * Returns the subscriberData parameter that was provided when 
+ * Hub.subscribe was called.
+ *
+ * @param subscriberID
+ *     The subscriberID of a subscription
+ * 
+ * @returns subscriberData
+ * @type {*}
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
+ */
+OpenAjax.hub.ManagedHub.prototype.getSubscriberData = function( subscriberID )
+{
+    this._assertConn();
+    var path = subscriberID.split(".");
+    var sid = path.pop();
+    var sub = this._getSubscriptionObject( this._subscriptions, path, 0, sid );
+    if ( sub ) 
+        return sub.data;
+    throw new Error( OpenAjax.hub.Error.NoSubscription );
+}
+
+/**
+ * Returns the scope associated with a specified subscription.  This scope will
+ * be used when invoking the 'onData' callback supplied to Hub.subscribe().
+ *
+ * @param subscriberID
+ *     The subscriberID of a subscription
+ * 
+ * @returns scope
+ * @type {*}
+ * 
+ * @throws {OpenAjax.hub.Error.Disconnected} if this Hub instance is not in CONNECTED state
+ * @throws {OpenAjax.hub.Error.NoSubscription} if there is no such subscription
+ */
+OpenAjax.hub.ManagedHub.prototype.getSubscriberScope = function( subscriberID )
+{
+    this._assertConn();
+    var path = subscriberID.split(".");
+    var sid = path.pop();
+    var sub = this._getSubscriptionObject( this._subscriptions, path, 0, sid );
+    if ( sub ) 
+        return sub.scope;
+    throw new Error( OpenAjax.hub.Error.NoSubscription );
+}
+
+/**
+ * Returns the params object associated with this Hub instance.
+ * Allows mix-in code to access parameters passed into constructor that created
+ * this Hub instance.
+ *
+ * @returns params  the params object associated with this Hub instance
+ * @type {Object}
+ */
+OpenAjax.hub.ManagedHub.prototype.getParameters = function()
+{
+    return this._p;
+}
+
+
+/* PRIVATE FUNCTIONS */
+
+/**
+ * Send a message to a container's client. 
+ * This is an OAH subscriber's data callback. It is private to ManagedHub
+ * and serves as an adapter between the OAH 1.0 API and Container.sendToClient.
+ * 
+ * @param {String} topic Topic on which data was published
+ * @param {Object} data  Data to be delivered to the client
+ * @param {Object} sd    Object containing properties 
+ *     c: container to which data must be sent
+ *     sid: subscription ID within that container
+ * @param {Object} pcont  Publishing container, or null if this data was
+ *      published by the manager
+ */
+OpenAjax.hub.ManagedHub.prototype._sendToClient = function(topic, data, sd, pcont) 
+{
+    if (!this.isConnected()) {
+        return;
+    }
+    if ( this._invokeOnPublish( topic, data, pcont, sd.c ) ) {
+        sd.c.sendToClient( topic, data, sd.sid );
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._assertConn = function() 
+{
+    if (!this.isConnected()) {
+        throw new Error(OpenAjax.hub.Error.Disconnected);
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._assertPubTopic = function(topic) 
+{
+    if ( !topic || topic === "" || (topic.indexOf("*") != -1) ||
+        (topic.indexOf("..") != -1) ||  (topic.charAt(0) == ".") ||
+        (topic.charAt(topic.length-1) == "."))
+    {
+        throw new Error(OpenAjax.hub.Error.BadParameters);
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._assertSubTopic = function(topic) 
+{
+    if ( ! topic ) {
+        throw new Error(OpenAjax.hub.Error.BadParameters);
+    }
+    var path = topic.split(".");
+    var len = path.length;
+    for (var i = 0; i < len; i++) {
+        var p = path[i];
+        if ((p === "") ||
+           ((p.indexOf("*") != -1) && (p != "*") && (p != "**"))) {
+            throw new Error(OpenAjax.hub.Error.BadParameters);
+        }
+        if ((p == "**") && (i < len - 1)) {
+            throw new Error(OpenAjax.hub.Error.BadParameters);
+        }
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._invokeOnComplete = function( func, scope, item, success, errorCode )
+{
+    if ( func ) { // onComplete is optional
+        try {
+            scope = scope || window;
+            func.call( scope, item, success, errorCode );
+        } catch( e ) {
+            OpenAjax.hub._debugger();
+            this._log( "caught error from onComplete callback: " + e.message );
+        }
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._invokeOnPublish = function( topic, data, pcont, scont )
+{
+    try {
+        return this._p.onPublish.call( this._scope, topic, data, pcont, scont );
+    } catch( e ) {
+        OpenAjax.hub._debugger();
+        this._log( "caught error from onPublish callback to constructor: " + e.message );
+    }
+    return false;
+}
+
+OpenAjax.hub.ManagedHub.prototype._invokeOnSubscribe = function( topic, container )
+{
+    try {
+        return this._p.onSubscribe.call( this._scope, topic, container );
+    } catch( e ) {
+        OpenAjax.hub._debugger();
+        this._log( "caught error from onSubscribe callback to constructor: " + e.message );
+    }
+    return false;
+}
+
+OpenAjax.hub.ManagedHub.prototype._invokeOnUnsubscribe = function( container, managerSubID )
+{
+    if ( this._onUnsubscribe ) {
+        var topic = managerSubID.slice( 0, managerSubID.lastIndexOf(".") );
+        try {
+            this._onUnsubscribe.call( this._scope, topic, container );
+        } catch( e ) {
+            OpenAjax.hub._debugger();
+            this._log( "caught error from onUnsubscribe callback to constructor: " + e.message );
+        }
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._subscribe = function( topic, onData, scope, subscriberData ) 
+{
+    var handle = topic + "." + this._seq;
+    var sub = { scope: scope, cb: onData, data: subscriberData, sid: this._seq++ };
+    var path = topic.split(".");
+    this._recursiveSubscribe( this._subscriptions, path, 0, sub );
+    return handle;
+}
+
+OpenAjax.hub.ManagedHub.prototype._recursiveSubscribe = function(tree, path, index, sub) 
+{
+    var token = path[index];
+    if (index == path.length) {
+        sub.next = tree.s;
+        tree.s = sub;
+    } else { 
+        if (typeof tree.c == "undefined") {
+             tree.c = {};
+         }
+        if (typeof tree.c[token] == "undefined") {
+            tree.c[token] = { c: {}, s: null }; 
+            this._recursiveSubscribe(tree.c[token], path, index + 1, sub);
+        } else {
+            this._recursiveSubscribe( tree.c[token], path, index + 1, sub);
+        }
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._publish = function( topic, data, pcont )
+{
+    // if we are currently handling a publish event, then queue this request
+    // and handle later, one by one
+    if ( this._isPublishing ) {
+        this._pubQ.push( { t: topic, d: data, p: pcont } );
+        return;
+    }
+    
+    this._safePublish( topic, data, pcont );
+    
+    while ( this._pubQ.length > 0 ) {
+        var pub = this._pubQ.shift();
+        this._safePublish( pub.t, pub.d, pub.p );
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._safePublish = function( topic, data, pcont )
+{
+    this._isPublishing = true;
+    var path = topic.split(".");
+    this._recursivePublish( this._subscriptions, path, 0, topic, data, pcont );
+    this._isPublishing = false;
+}
+
+OpenAjax.hub.ManagedHub.prototype._recursivePublish = function(tree, path, index, name, msg, pcont) 
+{
+    if (typeof tree != "undefined") {
+        var node;
+        if (index == path.length) {
+            node = tree;
+        } else {
+            this._recursivePublish(tree.c[path[index]], path, index + 1, name, msg, pcont);
+            this._recursivePublish(tree.c["*"], path, index + 1, name, msg, pcont);
+            node = tree.c["**"];
+        }
+        if (typeof node != "undefined") {
+            var sub = node.s;
+            while ( sub ) {
+                var sc = sub.scope;
+                var cb = sub.cb;
+                var d = sub.data;
+                if (typeof cb == "string") {
+                    // get a function object
+                    cb = sc[cb];
+                }
+                cb.call(sc, name, msg, d, pcont);
+                sub = sub.next;
+            }
+        }
+    }
+}
+
+OpenAjax.hub.ManagedHub.prototype._unsubscribe = function( subscriptionID )
+{
+    var path = subscriptionID.split(".");
+    var sid = path.pop();
+    if ( ! this._recursiveUnsubscribe( this._subscriptions, path, 0, sid ) ) {
+        throw new Error( OpenAjax.hub.Error.NoSubscription );
+    }
+}
+
+/**
+ * @returns 'true' if properly unsubscribed; 'false' otherwise
+ */
+OpenAjax.hub.ManagedHub.prototype._recursiveUnsubscribe = function(tree, path, index, sid) 
+{
+    if ( typeof tree == "undefined" ) {
+        return false;
+    }
+    
+    if (index < path.length) {
+        var childNode = tree.c[path[index]];
+        if ( ! childNode ) {
+            return false;
+        }
+        this._recursiveUnsubscribe(childNode, path, index + 1, sid);
+        if ( ! childNode.s ) {
+            for (var x in childNode.c) {
+                return true;
+            }
+            delete tree.c[path[index]];    
+        }
+    } else {
+        var sub = tree.s;
+        var sub_prev = null;
+        var found = false;
+        while ( sub ) {
+            if ( sid == sub.sid ) {
+                found = true;
+                if ( sub == tree.s ) {
+                    tree.s = sub.next;
+                } else {
+                    sub_prev.next = sub.next;
+                }
+                break;
+            }
+            sub_prev = sub;
+            sub = sub.next;
+        }
+        if ( ! found ) {
+            return false;
+        }
+    }
+    
+    return true;
+}
+
+OpenAjax.hub.ManagedHub.prototype._getSubscriptionObject = function( tree, path, index, sid )
+{
+    if (typeof tree != "undefined") {
+        if (index < path.length) {
+            var childNode = tree.c[path[index]];
+            return this._getSubscriptionObject(childNode, path, index + 1, sid);
+        }
+
+        var sub = tree.s;
+        while ( sub ) {
+            if ( sid == sub.sid ) {
+                return sub;
+            }
+            sub = sub.next;
+        }
+    }
+    return null;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Container
+ * @constructor
+ * 
+ * Container represents an instance of a manager-side object that contains and
+ * communicates with a single client of the hub. The container might be an inline
+ * container, an iframe FIM container, or an iframe PostMessage container, or
+ * it might be an instance of some other implementation.
+ *
+ * @param {OpenAjax.hub.ManagedHub} hub
+ *    Managed Hub instance
+ * @param {String} clientID
+ *    A string ID that identifies a particular client of a Managed Hub. Unique
+ *    within the context of the ManagedHub.
+ * @param {Object} params  
+ *    Parameters used to instantiate the Container.
+ *    Once the constructor is called, the params object belongs exclusively to
+ *    the Container. The caller MUST not modify it.
+ *    Implementations of Container may specify additional properties
+ *    for the params object, besides those identified below.
+ *    The following params properties MUST be supported by all Container 
+ *    implementations:
+ * @param {Function} params.Container.onSecurityAlert
+ *    Called when an attempted security breach is thwarted.  Function is defined
+ *    as follows:  function(container, securityAlert)
+ * @param {Function} [params.Container.onConnect]
+ *    Called when the client connects to the Managed Hub.  Function is defined
+ *    as follows:  function(container)
+ * @param {Function} [params.Container.onDisconnect]
+ *    Called when the client disconnects from the Managed Hub.  Function is
+ *    defined as follows:  function(container)
+ * @param {Object} [params.Container.scope]
+ *    Whenever one of the Container's callback functions is called, references
+ *    to "this" in the callback will refer to the scope object. If no scope is
+ *    provided, default is window.
+ * @param {Function} [params.Container.log]
+ *    Optional logger function. Would be used to log to console.log or
+ *    equivalent. 
+ *
+ * @throws {OpenAjax.hub.Error.BadParameters}   if required params are not
+ *   present or null
+ * @throws {OpenAjax.hub.Error.Duplicate}   if a Container with this clientID
+ *   already exists in the given Managed Hub
+ * @throws {OpenAjax.hub.Error.Disconnected}   if ManagedHub is not connected
+ */
+//OpenAjax.hub.Container = function( hub, clientID, params ) {}
+
+/**
+ * Send a message to the client inside this container. This function MUST only
+ * be called by ManagedHub. 
+ * 
+ * @param {String} topic
+ *    The topic name for the published message
+ * @param {*} data
+ *    The payload. Can be any JSON-serializable value.
+ * @param {String} containerSubscriptionId
+ *    Container's ID for a subscription, from previous call to
+ *    subscribeForClient()
+ */
+//OpenAjax.hub.Container.prototype.sendToClient = function( topic, data, containerSubscriptionId ) {}
+
+/**
+ * Shut down a container. remove does all of the following:
+ * - disconnects container from HubClient
+ * - unsubscribes from all of its existing subscriptions in the ManagedHub
+ * 
+ * This function is only called by ManagedHub.removeContainer
+ * Calling this function does NOT cause the container's onDisconnect callback to
+ * be invoked.
+ */
+//OpenAjax.hub.Container.prototype.remove = function() {}
+
+/**
+ * Returns true if the given client is connected to the managed hub.
+ * Else returns false.
+ *
+ * @returns true if the client is connected to the managed hub
+ * @type boolean
+ */
+//OpenAjax.hub.Container.prototype.isConnected = function() {}
+
+/**
+ * Returns the clientID passed in when this Container was instantiated.
+ *
+ * @returns The clientID
+ * @type {String}  
+ */
+//OpenAjax.hub.Container.prototype.getClientID = function() {}
+
+/**
+ * If DISCONNECTED:
+ * Returns null
+ * If CONNECTED:
+ * Returns the origin associated with the window containing the HubClient
+ * associated with this Container instance. The origin has the format
+ *  
+ * [protocol]://[host]
+ * 
+ * where:
+ * 
+ * [protocol] is "http" or "https"
+ * [host] is the hostname of the partner page.
+ * 
+ * @returns Partner's origin
+ * @type {String}
+ */
+//OpenAjax.hub.Container.prototype.getPartnerOrigin = function() {}
+
+/**
+ * Returns the params object associated with this Container instance.
+ *
+ * @returns params
+ *    The params object associated with this Container instance
+ * @type {Object}
+ */
+//OpenAjax.hub.Container.prototype.getParameters = function() {}
+
+/**
+ * Returns the ManagedHub to which this Container belongs.
+ *
+ * @returns ManagedHub
+ *         The ManagedHub object associated with this Container instance
+ * @type {OpenAjax.hub.ManagedHub}
+ */
+//OpenAjax.hub.Container.prototype.getHub = function() {}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/*
+ * Unmanaged Hub
+ */
+
+/**
+ * OpenAjax.hub._hub is the default ManagedHub instance that we use to 
+ * provide OAH 1.0 behavior. 
+ */
+OpenAjax.hub._hub = new OpenAjax.hub.ManagedHub({ 
+    onSubscribe: function(topic, ctnr) { return true; },
+    onPublish: function(topic, data, pcont, scont) { return true; }
+});
+
+/**
+ * Subscribe to a topic.
+ *
+ * @param {String} topic
+ *     A valid topic string. MAY include wildcards.
+ * @param {Function|String} onData
+ *     Callback function that is invoked whenever an event is published on the
+ *     topic.  If 'onData' is a string, then it represents the name of a
+ *     function on the 'scope' object.
+ * @param {Object} [scope]
+ *     When onData callback is invoked,
+ *     the JavaScript "this" keyword refers to this scope object.
+ *     If no scope is provided, default is window.
+ * @param {*} [subscriberData]
+ *     Client application provides this data, which is handed
+ *     back to the client application in the subscriberData
+ *     parameter of the onData callback function.
+ * 
+ * @returns {String} Identifier representing the subscription.
+ * 
+ * @throws {OpenAjax.hub.Error.BadParameters} if the topic is invalid
+ *     (e.g.contains an empty token)
+ */
+OpenAjax.hub.subscribe = function(topic, onData, scope, subscriberData) 
+{
+    // resolve the 'onData' function if it is a string
+    if ( typeof onData === "string" ) {
+        scope = scope || window;
+        onData = scope[ onData ] || null;
+    }
+    
+    return OpenAjax.hub._hub.subscribe( topic, onData, scope, null, subscriberData );
+}
+
+/**
+ * Unsubscribe from a subscription.
+ *
+ * @param {String} subscriptionID
+ *     Subscription identifier returned by subscribe()
+ *     
+ * @throws {OpenAjax.hub.Error.NoSubscription} if no such subscription is found
+ */
+OpenAjax.hub.unsubscribe = function(subscriptionID) 
+{
+    return OpenAjax.hub._hub.unsubscribe( subscriptionID );
+}
+
+/**
+ * Publish an event on a topic.
+ *
+ * @param {String} topic
+ *     A valid topic string. MUST NOT include wildcards.
+ * @param {*} data
+ *     Valid publishable data.
+ *     
+ * @throws {OpenAjax.hub.Error.BadParameters} if the topic cannot be published
+ *     (e.g. contains wildcards or empty tokens)
+ */
+OpenAjax.hub.publish = function(topic, data) 
+{
+    OpenAjax.hub._hub.publish(topic, data);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// Register the OpenAjax Hub itself as a library.
+OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "2.0", {});
+
+} // !OpenAjax.hub

Copied: shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/crypto.js (from r998422, shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.4/crypto.js)
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/crypto.js?p2=shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/crypto.js&p1=shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.4/crypto.js&r1=998422&r2=1005506&rev=1005506&view=diff
==============================================================================
    (empty)

Added: shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/feature.xml
URL: http://svn.apache.org/viewvc/shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/feature.xml?rev=1005506&view=auto
==============================================================================
--- shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/feature.xml (added)
+++ shindig/branches/2.0.x/extras/src/main/javascript/features-extras/org.openajax.hub-2.0.5/feature.xml Thu Oct  7 15:54:09 2010
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+-->
+<feature>
+  <name>org.openajax.hub-2.0.5</name>
+  <dependency>rpc</dependency>
+  <gadget>
+    <script src="OpenAjax-mashup.js"/>
+    <script src="iframe.js"/>
+    <script src="crypto.js"/>
+  </gadget>
+  <container>
+    <script src="OpenAjax-mashup.js"/>
+    <script src="iframe.js"/>
+    <script src="crypto.js"/>
+  </container>
+</feature>