diff --git a/opensocial/shindig.orng.properties b/opensocial/shindig.orng.properties new file mode 100644 index 000000000..366c26c5e --- /dev/null +++ b/opensocial/shindig.orng.properties @@ -0,0 +1,190 @@ +# 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. + +################################################################## +# +# Note from UCSF. Compare this to the latest in shindig-common/conf/shindig.properties +# whenever you download a new version of shindig +# +################################################################## + +# Location of feature manifests (comma separated) +shindig.features.default=res://features/features.txt + +# Location of container configurations (comma separated) +#shindig.containers.default=res://containers/default/container.js +shindig.containers.default=res://orng-container.js + +# A file containing blacklisted gadgets. +shindig.blacklist.file= + +### Inbound OAuth support +# The URL base to use for full OAuth support (three-legged) +shindig.oauth.base-url=/oauth/ +shindig.oauth.authorize-action=/WEB-INF/authorize.jsp + +### Outbound OAuth support +shindig.signing.state-key= +shindig.signing.key-name= +shindig.signing.key-file= +shindig.signing.global-callback-url=http://localhost:8080/shindigorng/gadgets/oauthcallback +shindig.signing.enable-signed-callbacks=true + +# Set to true if you want to allow the use of 3-legged OAuth tokens when viewer != owner. +# This setting is not recommeneded for pages that allow user-controlled javascript, since +# that javascript could be used to make unauthorized requests on behalf of the viewer of the page +shindig.signing.viewer-access-tokens-enabled=false + +# If enabled here, configuration values can be found in container configuration files. +shindig.locked-domain.enabled=false + +# TODO: This needs to be moved to container configuration. +# Note by Eric. This is set up to now exclude everything and include nothing +shindig.content-rewrite.only-allow-excludes=false +shindig.content-rewrite.include-urls=.* +shindig.content-rewrite.exclude-urls= +shindig.content-rewrite.include-tags=body,embed,img,input,link,script,style +shindig.content-rewrite.expires=86400 +shindig.content-rewrite.proxy-url=/shindigorng/gadgets/proxy?container=default&url= +shindig.content-rewrite.concat-url=/shindigorng/gadgets/concat?container=default& +shindig.content-rewrite.enable-split-js-concat=false + +# +# Default set of forced libs to allow for better caching +# +# NOTE: setting this causes the EndToEnd test to fail the opensocial-templates test +shindig.gadget-rewrite.default-forced-libs=core:rpc +shindig.gadget-rewrite.default-forced-libs= + +# +# Allow supported JavaScript features required by a gadget to be externalized on demand +shindig.gadget-rewrite.externalize-feature-libs=true + +# Configuration for image rewriter +shindig.image-rewrite.max-inmem-bytes = 1048576 +shindig.image-rewrite.max-palette-size = 256 +shindig.image-rewrite.allow-jpeg-conversion = true +shindig.image-rewrite.jpeg-compression = 0.75 +shindig.image-rewrite.min-threshold-bytes = 200 + +# Configuration for the os:Flash tag +shindig.flash.min-version = 9.0.115 + +# Configuration for template rewriter +shindig.template-rewrite.extension-tag-namespace=http://ns.opensocial.org/2009/extensions + +# These values provide default TTLs for HTTP responses that don't use caching headers. +shindig.cache.http.defaultTtl=3600000 +shindig.cache.http.negativeCacheTtl=60000 + +# A default refresh interval for XML files, since there is no natural way for developers to +# specify this value, and most HTTP responses don't include good cache control headers. +shindig.cache.xml.refreshInterval=300000 + +# Add entries in the form shindig.cache.lru..capacity to specify capacities for different +# caches when using the LruCacheProvider. +# It is highly recommended that the EhCache implementation be used instead of the LRU cache. +shindig.cache.lru.default.capacity=1000 +shindig.cache.lru.expressions.capacity=1000 +shindig.cache.lru.gadgetSpecs.capacity=1000 +shindig.cache.lru.messageBundles.capacity=1000 +shindig.cache.lru.httpResponses.capacity=10000 + +# The location of the EhCache configuration file. +shindig.cache.ehcache.config=res://org/apache/shindig/common/cache/ehcache/ehcacheConfig.xml + +# True to enable JMX integration with cache stats +shindig.cache.ehcache.jmx.enabled=true + +# true to enable JMX stats. +shindig.cache.ehcache.jmx.stats=true + +# true to skip expensive encoding detection. +# if true, will only attempt to validate utf-8. Assumes all other encodings are ISO-8859-1. +shindig.http.fast-encoding-detection=true + +# Configuration for the HttpFetcher +# Connection timeout, in milliseconds, for requests. +shindig.http.client.connection-timeout-ms=5000 + +# Maximum size, in bytes, of the object we fetched, 0 == no limit +shindig.http.client.max-object-size-bytes=0 + +# Strict-mode parsing for proxy and concat URIs ensures that the authority/host and path +# for the URIs match precisely what is found in the container config for it. This is +# useful where statistics and traffic routing patterns, typically in large installations, +# key on hostname (and occasionally path). Enforcing this does come at the cost that +# mismatches break, which in turn mandates that URI generation always happen in consistent +# fashion, ie. by the class itself or tightly controlled code. +shindig.uri.proxy.use-strict-parsing=false +shindig.uri.concat.use-strict-parsing=false + +# Host:port of the proxy to use while fetching urls. Leave blank if proxy is +# not to be used. +org.apache.shindig.gadgets.http.basicHttpFetcherProxy= + +org.apache.shindig.serviceExpirationDurationMinutes=60 + +# +# Older versions of shindig used 'data' in the json-rpc response format +# The spec calls for using 'result' instead, however to avoid breakage we +# allow you to set it back to the old way here +# +# valid values are +# result - new form +# data - old broken form +# both - return both fields for full compatibility +# +shindig.json-rpc.result-field=result + +# Remap "Internal server error"s received from the basicHttpFetcherProxy server to +# "Bad Gateway error"s, so that it is clear to the user that the proxy server is +# the one that threw the exception. +shindig.accelerate.remapInternalServerError=true +shindig.proxy.remapInternalServerError=true + +shindig.signing.key-file=/shindig/openssl/oauthkey.pem +shindig.signing.key-name= + +#################################################################################### +# +# Open Research Networking Gadgets Items +# +##################################################################################### + +# orng.system must be set to Profiles or VIVO +#orng.system = Profiles +orng.system = VIVO + +# orng.dbDriver is likely com.microsoft.sqlserver.jdbc.SQLServerDriver for Profiles and com.mysql.jdbc.Driver for VIVO +#orng.dbDriver = com.microsoft.sqlserver.jdbc.SQLServerDriver +orng.dbDriver = com.mysql.jdbc.Driver +orng.dbURL = jdbc:mysql://localhost/vitrodb +orng.dbUser = vitrodb +orng.dbPassword = vitrodb +orng.tokenservice.port = 8777 +# orng.RDFConverter = elda | babel +orng.RDFConverter = elda +#orng.RDFConverter = babel + +# until Profiles has RDF +orng.profilesXMLService = http://dev-profiles.ucsf.edu/api_100810/ProfileService.svc/ProfileSearch +orng.profilesRDF = true; + + + + diff --git a/opensocial/shindig_example_gadgets.sql b/opensocial/shindig_example_gadgets.sql new file mode 100644 index 000000000..51d404de6 --- /dev/null +++ b/opensocial/shindig_example_gadgets.sql @@ -0,0 +1,28 @@ + +-- Add some gadgets to play with ------------------------ +-- + +INSERT INTO `shindig_apps` (`appid`, `name`, `url`, `PersonFilterID`, `enabled`, `channels`) VALUES +(100, 'Google Search', 'http://dev-profiles.ucsf.edu/apps/GoogleSearch.xml', NULL, 1, NULL), +(101, 'Featured Presentations', 'http://dev-profiles.ucsf.edu/apps/SlideShare.xml', NULL, 1, NULL), +(102, 'Faculty Mentor', 'http://dev-profiles.ucsf.edu/apps/Mentor.xml', NULL, 1, NULL), +(103, 'Websites', 'http://dev-profiles.ucsf.edu/apps/Links.xml', NULL, 1, NULL), +(104, 'Profile List', 'http://dev-profiles.ucsf.edu/apps/ProfileListTool.xml', NULL, 1, 'JSONPersonIds'), +(105, 'Publication Export', 'http://dev-profiles.ucsf.edu/apps/PubExportTool.xml', NULL, 1, 'JSONPubMedIds'), +(106, 'RDF Test Gadget', 'http://dev-profiles.ucsf.edu/gadgets/RDFTest.xml', NULL, 1, NULL); + +INSERT INTO `shindig_app_views` (`appid`, `viewer_req`, `owner_req`, `page`, `view`, `closed_width`, `open_width`, `start_closed`, `chromeId`, `display_order`) VALUES +(100, NULL, NULL, 'search', NULL, 600, 600, 1, 'gadgets-search', NULL), +(101, NULL, 'R', 'individual', 'profile', 291, 590, 1, 'gadgets-view', 3), +(101, NULL, NULL, 'individual-EDIT-MODE', 'home', 700, 700, 1, 'gadgets-edit', NULL), +(102, NULL, 'R', 'individual', 'profile', 291, 590, 1, 'gadgets-view', 2), +(102, NULL, NULL, 'individual-EDIT-MODE', 'home', 700, 700, 1, 'gadgets-edit', NULL), +(103, NULL, NULL, 'individual-EDIT-MODE', 'home', 700, 700, 1, 'gadgets-edit', NULL), +(103, NULL, 'R', 'individual', 'profile', 291, 590, 0, 'gadgets-view', 1), +(104, 'U', NULL, 'search', 'small', 160, 160, 0, 'gadgets-tools', NULL), +(104, 'U', NULL, 'gadgetDetails', 'canvas', 700, 700, 0, 'gadgets-detail', NULL), +(104, 'U', NULL, 'SimilarPeople.aspx', 'small', 160, 160, 0, 'gadgets-tools', NULL), +(104, 'U', NULL, 'individual', 'small', 160, 160, 0, 'gadgets-view', NULL), +(104, 'U', NULL, 'CoAuthors.aspx', 'small', 160, 160, 0, 'gadgets-tools', NULL), +(105, 'U', NULL, 'individual', 'small', 160, 160, 0, 'gadgets-view', NULL), +(105, 'U', NULL, 'gadgetDetails', 'canvas', 700, 700, 0, 'gadgets-detail', NULL); diff --git a/opensocial/shindig_orng_tables.sql b/opensocial/shindig_orng_tables.sql new file mode 100644 index 000000000..ed52ea340 --- /dev/null +++ b/opensocial/shindig_orng_tables.sql @@ -0,0 +1,145 @@ + +-- +-- Table structure for table `shindig_activity` +-- + +CREATE TABLE IF NOT EXISTS `shindig_activity` ( + `activityId` int(11) NOT NULL AUTO_INCREMENT, + `userId` varchar(255) default NULL, + `appId` int(11) default NULL, + `createdDT` datetime default NULL, + `activity` text, + PRIMARY KEY (`activityId`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `shindig_appdata` +-- + +CREATE TABLE IF NOT EXISTS `shindig_appdata` ( + `userId` varchar(255) NOT NULL, + `appId` int(11) NOT NULL, + `keyname` varchar(255) NOT NULL, + `value` varchar(4000) default NULL, + `createdDT` datetime default NULL, + `updatedDT` datetime default NULL, + KEY `userId` (`userId`,`appId`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `shindig_apps` +-- + +CREATE TABLE IF NOT EXISTS `shindig_apps` ( + `appid` int(11) NOT NULL, + `name` varchar(255) NOT NULL, + `url` varchar(255) NOT NULL, + `PersonFilterID` int(11) default NULL, + `enabled` tinyint(1) NOT NULL default '1', + `channels` varchar(255) default NULL, + PRIMARY KEY (`appid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `shindig_app_registry` +-- + +CREATE TABLE IF NOT EXISTS `shindig_app_registry` ( + `appid` int(11) NOT NULL, + `personId` varchar(255) NOT NULL, + `createdDT` datetime NOT NULL, + PRIMARY KEY (`appid`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `shindig_app_views` +-- + +CREATE TABLE IF NOT EXISTS `shindig_app_views` ( + `appid` int(11) NOT NULL, + `viewer_req` char(1) default NULL, + `owner_req` char(1) default NULL, + `page` varchar(50) default NULL, + `view` varchar(50) default NULL, + `closed_width` int(11) default NULL, + `open_width` int(11) default NULL, + `start_closed` tinyint(1) default NULL, + `chromeId` varchar(50) default NULL, + `display_order` int(11) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `shindig_messages` +-- + +CREATE TABLE IF NOT EXISTS `shindig_messages` ( + `msgId` varchar(255) NOT NULL, + `senderId` varchar(255) default NULL, + `recipientId` varchar(255) default NULL, + `coll` varchar(255) default NULL, + `title` varchar(255) default NULL, + `body` varchar(4000) default NULL, + `createdDT` datetime default NULL, + PRIMARY KEY (`msgId`) +) ENGINE=MyISAM DEFAULT CHARSET=latin1; + +-- -------------------------------------------------------- + + +DELIMITER // +CREATE PROCEDURE shindig_registerAppPerson (uid varchar(255), aid INT, v BOOL) +BEGIN + IF (v) + THEN + INSERT INTO shindig_app_registry (appId, personId, createdDT) values (aid, uid, now()); + ELSE + DELETE FROM shindig_app_registry where appId = aid AND personId = uid; + END IF; +END // +DELIMITER ; + +DELIMITER // +CREATE PROCEDURE shindig_upsertAppData(uid varchar(255), aid INT, kn varchar(255),v varchar(4000)) +BEGIN + DECLARE cnt int; + SELECT count(*) FROM shindig_appdata WHERE userId = uid AND appId = aid and keyname = kn INTO cnt; + IF (cnt > 0) + THEN + UPDATE shindig_appdata set `value` = v, updatedDT = NOW() WHERE userId = uid AND appId = aid and keyname = kn; + ELSE + INSERT INTO shindig_appdata (userId, appId, keyname, `value`) values (uid, aid, kn, v); + END IF; + -- if keyname is VISIBLE, do more + IF (kn = 'VISIBLE' AND v = 'Y') + THEN + CALL shindig_registerAppPerson(uid, aid, 1); + ELSEIF (kn = 'VISIBLE' ) + THEN + CALL shindig_registerAppPerson(uid, aid, 0); + END IF; +END // +DELIMITER ; + +DELIMITER // +CREATE PROCEDURE shindig_deleteAppData(uid varchar(255),aid INT, kn varchar(255)) +BEGIN + DELETE FROM shindig_appdata WHERE userId = uid AND appId = aid and keyname = kn; + -- if keyname is VISIBLE, do more + IF (kn = 'VISIBLE' ) + THEN + CALL shindig_registerAppPerson(uid, aid, 0); + END IF; +END // +DELIMITER ; + + diff --git a/opensocial/shindigorng.war b/opensocial/shindigorng.war new file mode 100644 index 000000000..fa845ad02 Binary files /dev/null and b/opensocial/shindigorng.war differ diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java index ee4e28960..8d4f14774 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualResponseBuilder.java @@ -2,9 +2,15 @@ package edu.cornell.mannlib.vitro.webapp.controller.individual; +import java.io.IOException; +import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; + import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; import edu.cornell.mannlib.vitro.webapp.beans.Individual; @@ -22,6 +28,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.web.beanswrappers.ReadOnlyBeansWrapper; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.IndividualTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividual; +import edu.ucsf.vitro.opensocial.OpenSocialManager; import freemarker.ext.beans.BeansWrapper; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; @@ -33,6 +40,9 @@ import freemarker.template.TemplateModelException; * TODO clean this up. */ class IndividualResponseBuilder { + private static final Log log = LogFactory + .getLog(IndividualResponseBuilder.class); + private static final Map namespaces = new HashMap() {{ put("display", VitroVocabulary.DISPLAY); put("vitro", VitroVocabulary.vitroURI); @@ -78,6 +88,24 @@ class IndividualResponseBuilder { //If special values required for individuals like menu, include values in template values body.putAll(getSpecialEditingValues()); + // VIVO OpenSocial Extension by UCSF + try { + OpenSocialManager openSocialManager = new OpenSocialManager(vreq, + itm.isEditable() ? "individual-EDIT-MODE" : "individual", itm.isEditable()); + openSocialManager.setPubsubData(OpenSocialManager.JSON_PERSONID_CHANNEL, + OpenSocialManager.buildJSONPersonIds(individual, "1 person found")); + body.put(OpenSocialManager.TAG_NAME, openSocialManager); + if (openSocialManager.isVisible()) { + body.put("bodyOnload", "my.init();"); + } + } catch (JSONException e) { + log.error("JSONException in doTemplate()", e); + } catch (IOException e) { + log.error("IOException in doTemplate()", e); + } catch (SQLException e) { + log.error("SQLException in doTemplate()", e); + } + String template = new IndividualTemplateLocator(vreq, individual).findTemplate(); return new TemplateResponseValues(template, body); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java index 7b29e95c0..5323223bd 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/PagedSearchController.java @@ -3,6 +3,7 @@ package edu.cornell.mannlib.vitro.webapp.search.controller; import java.io.IOException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -51,6 +52,7 @@ import edu.cornell.mannlib.vitro.webapp.search.beans.VitroQueryFactory; import edu.cornell.mannlib.vitro.webapp.search.solr.SolrSetup; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.LinkTemplateModel; import edu.cornell.mannlib.vitro.webapp.web.templatemodels.searchresult.IndividualSearchResult; +import edu.ucsf.vitro.opensocial.OpenSocialManager; /** * Paged search controller that uses Solr @@ -268,7 +270,24 @@ public class PagedSearchController extends FreemarkerHttpServlet { vreq.getServletPath(), pagingLinkParams)); } - String template = templateTable.get(format).get(Result.PAGED); + // VIVO OpenSocial Extension by UCSF + try { + OpenSocialManager openSocialManager = new OpenSocialManager(vreq, "search"); + // put list of people found onto pubsub channel + List ids = OpenSocialManager.getOpenSocialId(individuals); + openSocialManager.setPubsubData(OpenSocialManager.JSON_PERSONID_CHANNEL, + OpenSocialManager.buildJSONPersonIds(ids, "" + ids.size() + " people found")); + body.put("openSocial", openSocialManager); + if (openSocialManager.isVisible()) { + body.put("bodyOnload", "my.init();"); + } + } catch (IOException e) { + log.error("IOException in doTemplate()", e); + } catch (SQLException e) { + log.error("SQLException in doTemplate()", e); + } + + String template = templateTable.get(format).get(Result.PAGED); return new TemplateResponseValues(template, body); } catch (Throwable e) { diff --git a/webapp/src/edu/ucsf/vitro/opensocial/GadgetController.java b/webapp/src/edu/ucsf/vitro/opensocial/GadgetController.java new file mode 100644 index 000000000..89d6cea00 --- /dev/null +++ b/webapp/src/edu/ucsf/vitro/opensocial/GadgetController.java @@ -0,0 +1,100 @@ +package edu.ucsf.vitro.opensocial; + +import java.io.IOException; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; + +public class GadgetController extends FreemarkerHttpServlet { + + private static final long serialVersionUID = 1L; + private static final Log log = LogFactory.getLog(GadgetController.class); + + @Override + protected ResponseValues processRequest(VitroRequest vreq) { + if (vreq.getServletPath().endsWith("/sandbox")) { + boolean sandbox = "True".equalsIgnoreCase(ConfigurationProperties.getBean(vreq.getSession() + .getServletContext()).getProperty("OpenSocial.sandbox")); + if (!sandbox) { + return new ExceptionResponseValues( new Exception("Sandbox not available")); + } + return processGadgetSandbox(vreq); + } + else { + return processGadgetDetails(vreq); + } + } + + protected ResponseValues processGadgetDetails(VitroRequest vreq) { + try { + Map body = new HashMap(); + + body.put("title", "Gadget Details"); + // VIVO OpenSocial Extension by UCSF + try { + OpenSocialManager openSocialManager = new OpenSocialManager(vreq, "gadgetDetails"); + body.put(OpenSocialManager.TAG_NAME, openSocialManager); + if (openSocialManager.isVisible()) { + body.put("bodyOnload", "my.init();"); + } + } catch (IOException e) { + log.error("IOException in doTemplate()", e); + } catch (SQLException e) { + log.error("SQLException in doTemplate()", e); + } + + return new TemplateResponseValues("gadgetDetails.ftl", body); + + } catch (Throwable e) { + log.error(e, e); + return new ExceptionResponseValues(e); + } + } + + @Override + protected String getTitle(String siteName, VitroRequest vreq) { + return "Gadget Details"; + } + + protected ResponseValues processGadgetSandbox(VitroRequest vreq) { + if ("POST".equalsIgnoreCase(vreq.getMethod())) { + vreq.getSession().setAttribute(OpenSocialManager.OPENSOCIAL_GADGETS, vreq.getParameter("gadgetURLS")); + vreq.getSession().setAttribute(OpenSocialManager.OPENSOCIAL_DEBUG, vreq.getParameter("debug") != null); + vreq.getSession().setAttribute(OpenSocialManager.OPENSOCIAL_NOCACHE, vreq.getParameter("useCache") == null); + return new RedirectResponseValues("/"); + } + + Map body = new HashMap(); + body.put("title", "Gadget Sandbox"); + + try { + OpenSocialManager openSocialManager = new OpenSocialManager(vreq, "gadgetSandbox"); + String gadgetURLS = ""; + for (PreparedGadget gadget : openSocialManager.getVisibleGadgets()) + { + gadgetURLS += gadget.getGadgetURL() + System.getProperty("line.separator"); + } + body.put("gadgetURLS", gadgetURLS); + body.put(OpenSocialManager.TAG_NAME, openSocialManager); + } catch (IOException e) { + log.error("IOException in doTemplate()", e); + } catch (SQLException e) { + log.error("SQLException in doTemplate()", e); + } + + + return new TemplateResponseValues("gadgetLogin.ftl", body); + } + +} diff --git a/webapp/src/edu/ucsf/vitro/opensocial/GadgetSpec.java b/webapp/src/edu/ucsf/vitro/opensocial/GadgetSpec.java new file mode 100644 index 000000000..165fdf1c4 --- /dev/null +++ b/webapp/src/edu/ucsf/vitro/opensocial/GadgetSpec.java @@ -0,0 +1,204 @@ +package edu.ucsf.vitro.opensocial; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.dbcp.BasicDataSource; + +public class GadgetSpec { + private String openSocialGadgetURL; + private String name; + private int appId = 0; + private List channels = new ArrayList(); + private boolean unknownGadget = false; + private Map viewRequirements = new HashMap(); + + // For preloading + public GadgetSpec(int appId, String name, String openSocialGadgetURL, + List channels) { + this.appId = appId; + this.name = name; + this.openSocialGadgetURL = openSocialGadgetURL; + this.channels.addAll(channels); + } + + public GadgetSpec(int appId, String name, String openSocialGadgetURL, + String channelsStr) { + this(appId, name, openSocialGadgetURL, Arrays.asList(channelsStr != null + && channelsStr.length() > 0 ? channelsStr.split(" ") : new String[0])); + } + + public GadgetSpec(int appId, String name, String openSocialGadgetURL, + List channels, boolean unknownGadget, BasicDataSource ds) + throws SQLException { + this(appId, name, openSocialGadgetURL, channels); + this.unknownGadget = unknownGadget; + // Load gadgets from the DB first + if (!unknownGadget) { + Connection conn = null; + Statement stmt = null; + ResultSet rset = null; + + try { + String sqlCommand = "select page, viewer_req, owner_req, view, closed_width, open_width, start_closed, chromeId, display_order from shindig_app_views where appId = " + + appId; + conn = ds.getConnection(); + stmt = conn.createStatement(); + rset = stmt.executeQuery(sqlCommand); + while (rset.next()) { + viewRequirements.put( + rset.getString(1), + new GadgetViewRequirements(rset.getString(1), rset + .getString(2), rset.getString(3), rset + .getString(4), rset.getInt(5), rset + .getInt(6), rset.getBoolean(7), rset + .getString(8), rset.getInt(9))); + } + } finally { + try { + if (rset != null) { + rset.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + if (stmt != null) { + stmt.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + + public int getAppId() { + return appId; + } + + public String getName() { + return name; + } + + public String getGadgetURL() { + return openSocialGadgetURL; + } + + public List getChannels() { + return channels; + } + + public boolean listensTo(String channel) { // if an unknown gadget just say yes, + // we don't care about + // performance in this situation + return unknownGadget || channels.contains(channel); + } + + public GadgetViewRequirements getGadgetViewRequirements(String page) { + if (viewRequirements.containsKey(page)) { + return viewRequirements.get(page); + } + return null; + } + + public boolean show(String viewerId, String ownerId, String page, + BasicDataSource ds) throws SQLException { + boolean show = true; + // if there are no view requirements, go ahead and show it. We are + // likely testing out a new gadget + // if there are some, turn it off unless this page is + if (viewRequirements.size() > 0) { + show = false; + } + + if (viewRequirements.containsKey(page)) { + show = true; + GadgetViewRequirements req = getGadgetViewRequirements(page); + if ('U' == req.getViewerReq() && viewerId != null) { + show = false; + } else if ('R' == req.getViewerReq()) { + show &= isRegisteredTo(viewerId, ds); + } + if ('R' == req.getOwnerReq()) { + show &= isRegisteredTo(ownerId, ds); + } else if ('S' == req.getOwnerReq()) { + show &= (viewerId == ownerId); + } + } + return show; + } + + public boolean isRegisteredTo(String personId, BasicDataSource ds) + throws SQLException { + int count = 0; + + Connection conn = null; + Statement stmt = null; + ResultSet rset = null; + + try { + String sqlCommand = "select count(*) from shindig_app_registry where appId = " + + getAppId() + " and personId = '" + personId + "';"; + conn = ds.getConnection(); + stmt = conn.createStatement(); + rset = stmt.executeQuery(sqlCommand); + while (rset.next()) { + count = rset.getInt(1); + } + } finally { + try { + if (rset != null) { + rset.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + if (stmt != null) { + stmt.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + return (count == 1); + } + + public boolean fromSandbox() { + return unknownGadget; + } + + // who sees it? Return the viewerReq for the ProfileDetails page + public char getVisibleScope() { + GadgetViewRequirements req = getGadgetViewRequirements("/display"); + return req != null ? req.getViewerReq() : ' '; + } + + public String toString() { + return "" + this.appId + ":" + this.name + ":" + this.openSocialGadgetURL; + } + +} diff --git a/webapp/src/edu/ucsf/vitro/opensocial/GadgetViewRequirements.java b/webapp/src/edu/ucsf/vitro/opensocial/GadgetViewRequirements.java new file mode 100644 index 000000000..bedbc3ba9 --- /dev/null +++ b/webapp/src/edu/ucsf/vitro/opensocial/GadgetViewRequirements.java @@ -0,0 +1,67 @@ +package edu.ucsf.vitro.opensocial; + +public class GadgetViewRequirements { + private String page; + private char viewerReq; // U for User or null for no requirement + private char ownerReq; // R for Registered or null for no requirement + private String view; + private int closedWidth; + private int openWidth; + private boolean startClosed; + private String chromeId; + private int display_order; + + public GadgetViewRequirements(String page, char viewerReq, char ownerReq, + String view, int closedWidth, int openWidth, boolean startClosed, + String chromeId, int display_order) { + this.page = page; + this.viewerReq = viewerReq; + this.ownerReq = ownerReq; + this.view = view; + this.closedWidth = closedWidth; + this.openWidth = openWidth; + this.startClosed = startClosed; + this.chromeId = chromeId; + this.display_order = display_order; + } + + public GadgetViewRequirements(String page, String viewerReq, + String ownerReq, String view, int closedWidth, int openWidth, + boolean startClosed, String chromeId, int display_order) { + this(page, viewerReq != null ? viewerReq.charAt(0) : ' ', + ownerReq != null ? ownerReq.charAt(0) : ' ', view, closedWidth, + openWidth, startClosed, chromeId, display_order); + } + + public char getViewerReq() { + return viewerReq; + } + + public char getOwnerReq() { + return ownerReq; + } + + public String getView() { + return view; + } + + public int getClosedWidth() { + return closedWidth; + } + + public int getOpenWidth() { + return openWidth; + } + + public boolean getStartClosed() { + return startClosed; + } + + public String getChromeId() { + return chromeId; + } + + int getDisplayOrder() { + return display_order; + } +} diff --git a/webapp/src/edu/ucsf/vitro/opensocial/OpenSocialManager.java b/webapp/src/edu/ucsf/vitro/opensocial/OpenSocialManager.java new file mode 100644 index 000000000..16b35a5d0 --- /dev/null +++ b/webapp/src/edu/ucsf/vitro/opensocial/OpenSocialManager.java @@ -0,0 +1,483 @@ +package edu.ucsf.vitro.opensocial; + +import java.io.IOException; +import java.net.Socket; +import java.net.URLEncoder; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.dbcp.BasicDataSource; +import org.json.JSONException; +import org.json.JSONObject; + +import edu.cornell.mannlib.vedit.beans.LoginStatusBean; +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.individual.IndividualRequestAnalysisContextImpl; +import edu.cornell.mannlib.vitro.webapp.controller.individual.IndividualRequestAnalyzer; +import edu.cornell.mannlib.vitro.webapp.controller.individual.IndividualRequestInfo; + +public class OpenSocialManager { + public static final String SHINDIG_URL_PROP = "OpenSocial.shindigURL"; + + public static final String OPENSOCIAL_DEBUG = "OPENSOCIAL_DEBUG"; + public static final String OPENSOCIAL_NOCACHE = "OPENSOCIAL_NOCACHE"; + public static final String OPENSOCIAL_GADGETS = "OPENSOCIAL_GADGETS"; + + public static final String JSON_PERSONID_CHANNEL = "JSONPersonIds"; + public static final String JSON_PMID_CHANNEL = "JSONPubMedIds"; + public static final String TAG_NAME = "openSocial"; + + private static final String DEFAULT_DRIVER = "com.mysql.jdbc.Driver"; + + private List gadgets = new ArrayList(); + private Map pubsubdata = new HashMap(); + private String viewerId = null; + private String ownerId = null; + private boolean isDebug = false; + private boolean noCache = false; + private String pageName; + private ConfigurationProperties configuration; + + private BasicDataSource dataSource; + + public OpenSocialManager(VitroRequest vreq, String pageName) throws SQLException, IOException { + this(vreq, pageName, false); + } + + public OpenSocialManager(VitroRequest vreq, String pageName, boolean editMode) throws SQLException, IOException { + this.isDebug = vreq.getSession() != null + && Boolean.TRUE.equals(vreq.getSession().getAttribute(OPENSOCIAL_DEBUG)); + this.noCache = vreq.getSession() != null + && Boolean.TRUE.equals(vreq.getSession().getAttribute(OPENSOCIAL_NOCACHE)); + this.pageName = pageName; + + configuration = ConfigurationProperties.getBean(vreq.getSession() + .getServletContext()); + + if (configuration.getProperty(SHINDIG_URL_PROP) == null) { + // do nothing + return; + } + + // Analyze the request to figure out whose page we are viewing. + this.ownerId = figureOwnerId(vreq); + + // in editMode we need to set the viewer to be the same as the owner + // otherwise, the gadget will not be able to save appData correctly + if (editMode) { + this.viewerId = ownerId; + } + else { + UserAccount viewer = LoginStatusBean.getCurrentUser(vreq); + this.viewerId = viewer != null ? viewer.getUri() : null; + } + + boolean gadgetSandbox = "gadgetSandbox".equals(pageName); + String requestAppId = vreq.getParameter("appId"); + + Map dbApps = new HashMap(); + Map officialApps = new HashMap(); + + dataSource = new BasicDataSource(); + dataSource.setDriverClassName(DEFAULT_DRIVER); + dataSource.setUsername(configuration + .getProperty("VitroConnection.DataSource.username")); + dataSource.setPassword(configuration + .getProperty("VitroConnection.DataSource.password")); + dataSource.setUrl(configuration + .getProperty("VitroConnection.DataSource.url")); + + // Load gadgets from the DB first + Connection conn = null; + Statement stmt = null; + ResultSet rset = null; + try { + + String sqlCommand = "select appId, name, url, channels, enabled from shindig_apps"; + // if a specific app is requested, only grab it + if (requestAppId != null) { + sqlCommand += " where appId = " + requestAppId; + } + conn = dataSource.getConnection(); + stmt = conn.createStatement(); + rset = stmt.executeQuery(sqlCommand); + + while (rset.next()) { + GadgetSpec spec = new GadgetSpec(rset.getInt(1), + rset.getString(2), rset.getString(3), rset.getString(4)); + String gadgetFileName = getGadgetFileNameFromURL(rset + .getString(3)); + + dbApps.put(gadgetFileName, spec); + if (requestAppId != null || rset.getBoolean(5)) { + officialApps.put(gadgetFileName, spec); + } + } + } finally { + try { + if (rset != null) { + rset.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + if (stmt != null) { + stmt.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Add manual gadgets if there are any + // Note that this block of code only gets executed after someone fills in the + // gadget/sandbox form! + int moduleId = 0; + if (vreq.getSession() != null + && vreq.getSession().getAttribute(OPENSOCIAL_GADGETS) != null) { + String openSocialGadgetURLS = (String) vreq.getSession() + .getAttribute(OPENSOCIAL_GADGETS); + String[] urls = openSocialGadgetURLS.split(System.getProperty("line.separator")); + for (String openSocialGadgetURL : urls) { + if (openSocialGadgetURL.length() == 0) + continue; + int appId = 0; // if URL matches one in the DB, use DB provided + // appId, otherwise generate one + String gadgetFileName = getGadgetFileNameFromURL(openSocialGadgetURL); + String name = gadgetFileName; + List channels = new ArrayList(); + boolean unknownGadget = true; + if (dbApps.containsKey(gadgetFileName)) { + appId = dbApps.get(gadgetFileName).getAppId(); + name = dbApps.get(gadgetFileName).getName(); + channels = dbApps.get(gadgetFileName).getChannels(); + unknownGadget = false; + } else { + appId = openSocialGadgetURL.hashCode(); + } + // if they asked for a specific one, only let it in + if (requestAppId != null + && Integer.getInteger(requestAppId) != appId) { + continue; + } + GadgetSpec gadget = new GadgetSpec(appId, name, + openSocialGadgetURL, channels, unknownGadget, dataSource); + // only add ones that are visible in this context! + if (unknownGadget + || gadget.show(viewerId, ownerId, pageName, dataSource)) { + String securityToken = socketSendReceive(viewerId, ownerId, + "" + gadget.getAppId()); + gadgets.add(new PreparedGadget(gadget, this, moduleId++, + securityToken)); + } + } + } + + // if no manual one were added, use the ones from the DB + if (gadgets.size() == 0) { + // Load DB gadgets + if (gadgetSandbox) { + officialApps = dbApps; + } + for (GadgetSpec spec : officialApps.values()) { + GadgetSpec gadget = new GadgetSpec(spec.getAppId(), + spec.getName(), spec.getGadgetURL(), + spec.getChannels(), false, dataSource); + // only add ones that are visible in this context! + if (gadgetSandbox + || gadget.show(viewerId, ownerId, pageName, dataSource)) { + String securityToken = socketSendReceive(viewerId, ownerId, + "" + gadget.getAppId()); + gadgets.add(new PreparedGadget(gadget, this, moduleId++, + securityToken)); + } + } + } + + // sort the gadgets + Collections.sort(gadgets); + } + + private String figureOwnerId(VitroRequest vreq) { + IndividualRequestAnalyzer requestAnalyzer = new IndividualRequestAnalyzer(vreq, + new IndividualRequestAnalysisContextImpl(vreq)); + IndividualRequestInfo requestInfo = requestAnalyzer.analyze(); + Individual owner = requestInfo.getIndividual(); + return owner != null ? owner.getURI() : null; + } + + private String getGadgetFileNameFromURL(String url) { + String[] urlbits = url.split("/"); + return urlbits[urlbits.length - 1]; + } + + public boolean isDebug() { + return isDebug; + } + + public boolean noCache() { + return noCache; + } + + public String getOwnerId() { + return ownerId; + } + + public boolean hasGadgetListeningTo(String channel) { + for (PreparedGadget gadget : getVisibleGadgets()) { + if (gadget.getGadgetSpec().listensTo(channel)) { + return true; + } + } + return false; + } + + public static List getOpenSocialId(List individuals) { + List personIds = new ArrayList(); + for (Individual ind : individuals) { + personIds.add(ind.getURI()); + } + return personIds; + } + + // JSON Helper Functions + public static String buildJSONPersonIds(List personIds, + String message) throws JSONException { + JSONObject json = new JSONObject(); + json.put("message", message); + json.put("personIds", personIds); + return json.toString(); + } + + public static String buildJSONPersonIds(String personId, String message) throws JSONException { + List personIds = new ArrayList(); + personIds.add(personId); + return buildJSONPersonIds(personIds, message); + } + + public static String buildJSONPersonIds(Individual ind, String message) throws JSONException { + List personIds = new ArrayList(); + personIds.add(ind.getURI()); + return buildJSONPersonIds(personIds, message); + } + /**** + * public static String BuildJSONPubMedIds(Person person) { List + * pubIds = new List(); foreach (Publication pub in + * person.PublicationList) { foreach (PublicationSource pubSource in + * pub.PublicationSourceList) { if ("PubMed".Equals(pubSource.Name)) { + * pubIds.Add(Int32.Parse(pubSource.ID)); } } } Dictionary + * foundPubs = new Dictionary(); foundPubs.Add("pubIds", + * pubIds); foundPubs.Add("message", "PubMedIDs for " + + * person.Name.FullName); JavaScriptSerializer serializer = new + * JavaScriptSerializer(); return serializer.Serialize(foundPubs); } + ***/ + + public void setPubsubData(String key, String value) { + if (pubsubdata.containsKey(key)) { + pubsubdata.remove(key); + } + if (value != null && !value.isEmpty()) { + pubsubdata.put(key, value); + } + } + + public Map getPubsubData() { + return pubsubdata; + } + + public void removePubsubGadgetsWithoutData() { + // if any visible gadgets depend on pubsub data that isn't present, + // throw them out + List removedGadgets = new ArrayList(); + for (PreparedGadget gadget : gadgets) { + for (String channel : gadget.getGadgetSpec().getChannels()) { + if (!pubsubdata.containsKey(channel)) { + removedGadgets.add(gadget); + break; + } + } + } + for (PreparedGadget gadget : removedGadgets) { + gadgets.remove(gadget); + } + } + + public void removeGadget(String name) { + // if any visible gadgets depend on pubsub data that isn't present, + // throw them out + PreparedGadget gadgetToRemove = null; + for (PreparedGadget gadget : gadgets) { + if (name.equals(gadget.getName())) { + gadgetToRemove = gadget; + break; + } + } + gadgets.remove(gadgetToRemove); + } + + public String getPageName() { + return pageName; + } + + public String getIdToUrlMapJavascript() { + String retval = "var idToUrlMap = {"; + for (PreparedGadget gadget : gadgets) { + // retval += gadget.GetAppId() + ":'" + gadget.GetGadgetURL() + + // "', "; + retval += "'remote_iframe_" + gadget.getAppId() + "':'" + + gadget.getGadgetURL() + "', "; + } + return retval.substring(0, retval.length() - 2) + "};"; + } + + public boolean isVisible() { + // always have turned on for ProfileDetails.aspx because we want to + // generate the "profile was viewed" in Javascript (bot proof) + // regardless of any gadgets being visible, and we need this to be True + // for the shindig javascript libraries to load + return (configuration.getProperty(SHINDIG_URL_PROP) != null + && (getVisibleGadgets().size() > 0) || getPageName().equals( + "/display")); + } + + public List getVisibleGadgets() { + return gadgets; + } + + public void postActivity(int userId, String title) throws SQLException { + postActivity(userId, title, null, null, null); + } + + public void postActivity(int userId, String title, String body) throws SQLException { + postActivity(userId, title, body, null, null); + } + + public void postActivity(int userId, String title, String body, + String xtraId1Type, String xtraId1Value) throws SQLException { + Connection conn = null; + Statement stmt = null; + String sqlCommand = "INSERT INTO shindig_activity (userId, activity, xtraId1Type, xtraId1Value) VALUES ('" + + userId + "','" + + System.currentTimeMillis() + "" + title + "" + + (body != null ? "" + body + "" : "") + "','" + + xtraId1Type + "','" + xtraId1Value + "');"; + try { + conn = dataSource.getConnection(); + stmt = conn.createStatement(); + stmt.executeUpdate(sqlCommand); + } finally { + try { + if (stmt != null) { + stmt.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + try { + if (conn != null) { + conn.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + + private String socketSendReceive(String viewer, String owner, String gadget) + throws IOException { + // These keys need to match what you see in + // edu.ucsf.orng.shindig.service.SecureTokenGeneratorService in + // Shindig + String[] tokenService = configuration.getProperty( + "OpenSocial.tokenService").split(":"); + String request = "c=default" + (viewer != null ? "&v=" + URLEncoder.encode(viewer, "UTF-8") : "") + + (owner != null ? "&o=" + URLEncoder.encode(owner, "UTF-8") : "") + "&g=" + gadget + "\r\n"; + + // Create a socket connection with the specified server and port. + Socket s = new Socket(tokenService[0], + Integer.parseInt(tokenService[1])); + + // Send request to the server. + s.getOutputStream().write(request.getBytes()); + + // Receive the encoded content. + int bytes = 0; + String page = ""; + byte[] bytesReceived = new byte[256]; + + // The following will block until the page is transmitted. + while ((bytes = s.getInputStream().read(bytesReceived)) > 0) { + page += new String(bytesReceived, 0, bytes); + } + + return page; + } + + public String getContainerJavascriptSrc() { + return configuration.getProperty(SHINDIG_URL_PROP) + + "/gadgets/js/core:dynamic-height:osapi:pubsub:rpc:views:shindig-container.js?c=1" + + (isDebug ? "&debug=1" : ""); + } + + public String getGadgetJavascript() { + String lineSeparator = System.getProperty("line.separator"); + String gadgetScriptText = lineSeparator + + "var my = {};" + + lineSeparator + + "my.gadgetSpec = function(appId, name, url, secureToken, view, closed_width, open_width, start_closed, chrome_id, visible_scope) {" + + lineSeparator + "this.appId = appId;" + lineSeparator + + "this.name = name;" + lineSeparator + "this.url = url;" + + lineSeparator + "this.secureToken = secureToken;" + + lineSeparator + "this.view = view || 'default';" + + lineSeparator + "this.closed_width = closed_width;" + + lineSeparator + "this.open_width = open_width;" + + lineSeparator + "this.start_closed = start_closed;" + + lineSeparator + "this.chrome_id = chrome_id;" + lineSeparator + + "this.visible_scope = visible_scope;" + lineSeparator + "};" + + lineSeparator + "my.pubsubData = {};" + lineSeparator; + for (String key : getPubsubData().keySet()) { + gadgetScriptText += "my.pubsubData['" + key + "'] = '" + + getPubsubData().get(key) + "';" + lineSeparator; + } + gadgetScriptText += "my.openSocialURL = '" + + configuration.getProperty(SHINDIG_URL_PROP) + "';" + + lineSeparator + "my.debug = " + (isDebug() ? "1" : "0") + ";" + + lineSeparator + "my.noCache = " + (noCache() ? "1" : "0") + + ";" + lineSeparator + "my.gadgets = ["; + for (PreparedGadget gadget : getVisibleGadgets()) { + gadgetScriptText += "new my.gadgetSpec(" + gadget.getAppId() + ",'" + + gadget.getName() + "','" + gadget.getGadgetURL() + "','" + + gadget.getSecurityToken() + "','" + gadget.getView() + + "'," + gadget.getClosedWidth() + "," + + gadget.getOpenWidth() + "," + + (gadget.getStartClosed() ? "1" : "0") + ",'" + + gadget.getChromeId() + "','" + + gadget.getGadgetSpec().getVisibleScope() + "'), "; + } + gadgetScriptText = gadgetScriptText.substring(0, + gadgetScriptText.length() - 2) + + "];" + + lineSeparator; + + return gadgetScriptText; + } +} diff --git a/webapp/src/edu/ucsf/vitro/opensocial/PreparedGadget.java b/webapp/src/edu/ucsf/vitro/opensocial/PreparedGadget.java new file mode 100644 index 000000000..caab0b18b --- /dev/null +++ b/webapp/src/edu/ucsf/vitro/opensocial/PreparedGadget.java @@ -0,0 +1,123 @@ +package edu.ucsf.vitro.opensocial; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +public class PreparedGadget implements Comparable { + private GadgetSpec gadgetSpec; + private OpenSocialManager helper; + private int moduleId; + private String securityToken; + + public PreparedGadget(GadgetSpec gadgetSpec, OpenSocialManager helper, + int moduleId, String securityToken) { + this.gadgetSpec = gadgetSpec; + this.helper = helper; + this.moduleId = moduleId; + this.securityToken = securityToken; + } + + public int compareTo(PreparedGadget other) { + GadgetViewRequirements gvr1 = this.getGadgetViewRequirements(); + GadgetViewRequirements gvr2 = other.getGadgetViewRequirements(); + return ("" + this.getView() + (gvr1 != null ? gvr1.getDisplayOrder() + : Integer.MAX_VALUE)).compareTo("" + other.getView() + + (gvr2 != null ? gvr2.getDisplayOrder() : Integer.MAX_VALUE)); + } + + public GadgetSpec getGadgetSpec() { + return gadgetSpec; + } + + public String getSecurityToken() { + return securityToken; + } + + public int getAppId() { + return gadgetSpec.getAppId(); + } + + public String getName() { + return gadgetSpec.getName(); + } + + public int getModuleId() { + return moduleId; + } + + public String getGadgetURL() { + return gadgetSpec.getGadgetURL(); + } + + GadgetViewRequirements getGadgetViewRequirements() { + return gadgetSpec.getGadgetViewRequirements(helper.getPageName()); + } + + public String getView() { + GadgetViewRequirements reqs = getGadgetViewRequirements(); + if (reqs != null) { + return reqs.getView(); + } + // default behavior that will get invoked when there is no reqs. Useful + // for sandbox gadgets + else if (helper.getPageName().equals("individual-EDIT-MODE")) { + return "home"; + } else if (helper.getPageName().equals("individual")) { + return "profile"; + } else if (helper.getPageName().equals("gadgetDetails")) { + return "canvas"; + } else if (gadgetSpec.getGadgetURL().contains("Tool")) { + return "small"; + } else { + return null; + } + } + + public int getOpenWidth() { + GadgetViewRequirements reqs = getGadgetViewRequirements(); + return reqs != null ? reqs.getOpenWidth() : 0; + } + + public int getClosedWidth() { + GadgetViewRequirements reqs = getGadgetViewRequirements(); + return reqs != null ? reqs.getClosedWidth() : 0; + } + + public boolean getStartClosed() { + GadgetViewRequirements reqs = getGadgetViewRequirements(); + // if the page specific reqs are present, honor those. Otherwise defaut + // to true for regular gadgets, false for sandbox gadgets + return reqs != null ? reqs.getStartClosed() : !gadgetSpec.fromSandbox(); + } + + public String getChromeId() { + GadgetViewRequirements reqs = getGadgetViewRequirements(); + if (reqs != null) { + return reqs.getChromeId(); + } + // default behavior that will get invoked when there is no reqs. Useful + // for sandbox gadgets + else if (gadgetSpec.getGadgetURL().contains("Tool")) { + return "gadgets-tools"; + } else if (helper.getPageName().equals("individual-EDIT-MODE")) { + return "gadgets-edit"; + } else if (helper.getPageName().equals("individual")) { + return "gadgets-view"; + } else if (helper.getPageName().equals("gadgetDetails")) { + return "gadgets-detail"; + } else if (helper.getPageName().equals("search")) { + return "gadgets-search"; + } else { + return null; + } + } + + public String getCanvasURL() throws UnsupportedEncodingException { + return "~/gadget?appId=" + getAppId() + "&Person=" + + URLEncoder.encode(helper.getOwnerId(), "UTF-8"); + } + + public String toString() { + return "" + this.moduleId + ", (" + this.gadgetSpec.toString() + ")"; + } +} diff --git a/webapp/web/WEB-INF/web.xml b/webapp/web/WEB-INF/web.xml index ec690c2ab..61b95f719 100644 --- a/webapp/web/WEB-INF/web.xml +++ b/webapp/web/WEB-INF/web.xml @@ -1300,6 +1300,20 @@ /admin/getObjectClasses + + + GadgetController + edu.ucsf.vitro.opensocial.GadgetController + + + GadgetController + /gadget + + + GadgetController + /gadget/sandbox + + diff --git a/webapp/web/js/openSocial/shindig.js b/webapp/web/js/openSocial/shindig.js new file mode 100644 index 000000000..7809a3b7c --- /dev/null +++ b/webapp/web/js/openSocial/shindig.js @@ -0,0 +1,449 @@ +/* + Profiles Shindig Helper functions for gadget-to-container commands + + */ + + // dummy function so google analytics does not break for institutions who do not use it + +_gaq = {}; +_gaq.push = function(data) { // + }; + +// pubsub +gadgets.pubsubrouter.init(function(id) { + return my.gadgets[shindig.container.gadgetService.getGadgetIdFromModuleId(id)].url; + }, { + onSubscribe: function(sender, channel) { + setTimeout("my.onSubscribe('" + sender + "', '" + channel + "')", 3000); + // return true to reject the request. + return false; + }, + onUnsubscribe: function(sender, channel) { + //alert(sender + " unsubscribes from channel '" + channel + "'"); + // return true to reject the request. + return false; + }, + onPublish: function(sender, channel, message) { + // return true to reject the request. + + // track with google analytics + if (sender != '..' ) { + var moduleId = shindig.container.gadgetService.getGadgetIdFromModuleId(sender); + } + + if (channel == 'VISIBLE') { + var statusId = document.getElementById(sender + '_status'); + if (statusId) { + // only act on these in HOME view since they are only meant to be seen when viewer=owner + if (my.gadgets[moduleId].view != 'home') { + return true; + } + if (message == 'Y') { + statusId.style.color = 'GREEN'; + statusId.innerHTML = 'This section is VISIBLE'; + if (my.gadgets[moduleId].visible_scope == 'U') { + statusId.innerHTML += ' to UCSF'; + } + else { + statusId.innerHTML += ' to the public'; + } + } + else { + statusId.style.color = '#CC0000'; + statusId.innerHTML = 'This section is HIDDEN'; + if (my.gadgets[moduleId].visible_scope == 'U') { + statusId.innerHTML += ' from UCSF'; + } + else { + statusId.innerHTML += ' from the public'; + } + } + } + } + else if (channel == 'added' && my.gadgets[moduleId].view == 'home') { + if (message == 'Y') { + _gaq.push(['_trackEvent', my.gadgets[moduleId].name, 'SHOW', 'profile_edit_view']); + osapi.activities.create( + { 'userId': gadgets.util.getUrlParameters()['Person'], + 'appId': my.gadgets[moduleId].appId, + 'activity': {'postedTime': new Date().getTime(), 'title': 'added a gadget', 'body': 'added the ' + my.gadgets[moduleId].name + ' gadget to their profile' } + }).execute(function(response){}); + } + else { + _gaq.push(['_trackEvent', my.gadgets[moduleId].name, 'HIDE', 'profile_edit_view']); + } + } + else if (channel == 'status') { + // message should be of the form 'COLOR:Message Content' + var statusId = document.getElementById(sender + '_status'); + if (statusId) { + var messageSplit = message.split(':'); + if (messageSplit.length == 2) { + statusId.style.color = messageSplit[0]; + statusId.innerHTML = messageSplit[1]; + } + else { + statusId.innerHTML = message; + } + } + } + else if (channel == 'analytics') { + // publish to google analytics + // message should be JSON encoding object with required action and optional label and value + // as documented here: http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html + // note that event category will be set to the gadget name automatically by this code + // Note: message will be already converted to an object + if (message.hasOwnProperty('value')) { + _gaq.push(['_trackEvent', my.gadgets[moduleId].name, message.action, message.label, message.value]); + } + else if (message.hasOwnProperty('label')) { + _gaq.push(['_trackEvent', my.gadgets[moduleId].name, message.action, message.label]); + } + else { + _gaq.push(['_trackEvent', my.gadgets[moduleId].name, message.action]); + } + } + else if (channel == 'profile') { + _gaq.push(['_trackEvent', my.gadgets[moduleId].name, 'go_to_profile', message]); + document.location.href = '/' + location.pathname.split('/')[1] + '/display/n' + message; + } + else if (channel == 'JSONPersonIds' || channel == 'JSONPubMedIds') { + // do nothing, no need to alert + } + else { + alert(sender + " publishes '" + message + "' to channel '" + channel + "'"); + } + return false; + } +}); + +// helper functions +my.findGadgetsAttachingTo = function(chromeId) { + var retval = []; + for (var i = 0; i < my.gadgets.length; i++) { + if (my.gadgets[i].chrome_id == chromeId) { + retval[retval.length] = my.gadgets[i]; + } + } + return retval; +}; + +my.removeGadgets = function(gadgetsToRemove) { + for (var i = 0; i < gadgetsToRemove.length; i++) { + for (var j = 0; j < my.gadgets.length; j++) { + if (gadgetsToRemove[i].url == my.gadgets[j].url) { + my.gadgets.splice(j, 1); + break; + } + } + } +}; + +my.onSubscribe = function(sender, channel) { + // lookup pubsub data based on channel and if a match is found, publish the data to that channel after a delay + if (my.pubsubData[channel]) { + gadgets.pubsubrouter.publish(channel, my.pubsubData[channel]); + } + else { + alert(sender + " subscribes to channel '" + channel + "'"); + } + //PageMethods.onSubscribe(sender, channel, my.pubsubHint, my.CallSuccess, my.CallFailed); +}; + +my.removeParameterFromURL = function(url, parameter) { + var urlparts= url.split('?'); // prefer to use l.search if you have a location/link object + if (urlparts.length>=2) { + var prefix= encodeURIComponent(parameter)+'='; + var pars= urlparts[1].split(/[&;]/g); + for (var i= pars.length; i-->0;) //reverse iteration as may be destructive + if (pars[i].lastIndexOf(prefix, 0)!==-1) //idiom for string.startsWith + pars.splice(i, 1); + url= urlparts[0]+'?'+pars.join('&'); + } + return url; +}; + + // publish the people +my.CallSuccess = function(result) { + gadgets.pubsubrouter.publish('person', result); +}; + + // alert message on some failure +my.CallFailed = function(error) { + alert(error.get_message()); +}; + +my.requestGadgetMetaData = function(view, opt_callback) { + var request = { + context: { + country: "default", + language: "default", + view: view, + ignoreCache : my.noCache, + container: "default" + }, + gadgets: [] + }; + + for (var moduleId = 0; moduleId < my.gadgets.length; moduleId++) { + // only add those with matching views + if (my.gadgets[moduleId].view == view) { + request.gadgets[request.gadgets.length] = {'url': my.gadgets[moduleId].url, 'moduleId': moduleId}; + } + } + + var makeRequestParams = { + "CONTENT_TYPE" : "JSON", + "METHOD" : "POST", + "POST_DATA" : gadgets.json.stringify(request)}; + + gadgets.io.makeNonProxiedRequest(my.openSocialURL + "/gadgets/metadata", + function(data) { + data = data.data; + if (opt_callback) { + opt_callback(data); + } + }, + makeRequestParams, + "application/javascript" + ); +}; + +my.renderableGadgets = []; + +my.generateGadgets = function(metadata) { + // put them in moduleId order + for (var i = 0; i < metadata.gadgets.length; i++) { + var moduleId = metadata.gadgets[i].moduleId; + // Notes by Eric. Not sure if I should have to calculate this myself, but I will. + var height = metadata.gadgets[i].height; + var width = metadata.gadgets[i].width; + var viewPrefs = metadata.gadgets[i].views[my.gadgets[moduleId].view]; + if (viewPrefs) { + height = viewPrefs.preferredHeight || height; + width = viewPrefs.preferredWidth || width; + } + my.renderableGadgets[moduleId] = shindig.container.createGadget({'specUrl': metadata.gadgets[i].url, 'secureToken': my.gadgets[moduleId].secureToken, + 'title': metadata.gadgets[i].title, 'userPrefs': metadata.gadgets[i].userPrefs, + 'height': height, 'width': width, 'debug': my.debug}); + // set the metadata for easy access + my.renderableGadgets[moduleId].setMetadata(metadata.gadgets[i]); + } + // this will be called multiple times, only render when all gadgets have been processed + var ready = my.renderableGadgets.length == my.gadgets.length; + for (var i = 0; ready && i < my.renderableGadgets.length; i++) { + if (!my.renderableGadgets[i]) { + ready = false; + } + } + + if (ready) { + shindig.container.addGadgets(my.renderableGadgets ); + shindig.container.renderGadgets(); + } +}; + +my.init = function() { + // overwrite this RPC function. Do it at this level so that rpc.f (this.f) is accessible for getting module ID +// gadgets.rpc.register('requestNavigateTo', doProfilesNavigation); + + shindig.container.gadgetClass = ProfilesGadget; + shindig.container.layoutManager = new ProfilesLayoutManager(); + shindig.container.setNoCache(my.noCache); + shindig.container.gadgetService = new ProfilesGadgetService(); + + // since we render multiple views, we need to do somethign fancy by swapping out this value in getIframeUrl + shindig.container.setView('REPLACE_THIS_VIEW'); + + // do multiple times as needed if we have multiple views + // find out what views are being used and call requestGadgetMetaData for each one + var views = {}; + for (var moduleId = 0; moduleId < my.gadgets.length; moduleId++) { + var view = my.gadgets[moduleId].view; + if (!views[view]) { + views[view] = view; + my.requestGadgetMetaData(view, my.generateGadgets); + } + } +}; + +// ProfilesGadgetService + +ProfilesGadgetService = function() { + shindig.IfrGadgetService.call(this); +}; + +ProfilesGadgetService.inherits(shindig.IfrGadgetService); + +ProfilesGadgetService.prototype.requestNavigateTo = function(view, opt_params) { + var urlTemplate = gadgets.config.get('views')[view].urlTemplate; + var url = urlTemplate || 'OpenSocial.aspx?'; + + url += window.location.search.substring(1); + + // remove appId if present + url = my.removeParameterFromURL(url, 'appId'); + + // Add appId if the URL Template begins with the word 'gadget' + if (urlTemplate.toLowerCase().indexOf('gadget') == 0) { + var moduleId = shindig.container.gadgetService.getGadgetIdFromModuleId(this.f); + var appId = my.gadgets[moduleId].appId; + url += (url.indexOf('?') != url.length - 1 ? '&' : '') + 'appId=' + appId; + } + + if (opt_params) { + var paramStr = gadgets.json.stringify(opt_params); + if (paramStr.length > 0) { + url += (url.indexOf('?') != url.length - 1 ? '&' : '') + 'appParams=' + encodeURIComponent(paramStr); + } + } + if (url && document.location.href.indexOf(url) == -1) { + document.location.href = url; + } +}; + +// ProfilesGadget + +ProfilesGadget = function(opt_params) { + shindig.Gadget.call(this, opt_params); + this.debug = my.debug; + this.serverBase_ = my.openSocialURL + "/gadgets/"; + var gadget = this; + var subClass = shindig.IfrGadget; + this.metadata = {}; + for (var name in subClass) if (subClass.hasOwnProperty(name)) { + if (name == 'getIframeUrl') { + // we need to keep this old one + gadget['originalGetIframeUrl'] = subClass[name]; + } + else if (name != 'finishRender') { + gadget[name] = subClass[name]; + } + } +}; + +ProfilesGadget.inherits(shindig.BaseIfrGadget); + +ProfilesGadget.prototype.setMetadata = function(metadata) { + this.metadata = metadata; +}; + +ProfilesGadget.prototype.hasFeature = function(feature) { + for (var i = 0; i < this.metadata.features.length; i++) { + if (this.metadata.features[i] == feature) { + return true; + } + } + return false; +}; + +ProfilesGadget.prototype.getAdditionalParams = function() { + var params = ''; + for (var key in my.gadgets[this.id].additionalParams) { + params += '&' + key + '=' + my.gadgets[this.id].additionalParams[key]; + } + return params; +}; + +ProfilesGadget.prototype.finishRender = function(chrome) { + window.frames[this.getIframeId()].location = this.getIframeUrl(); + if (my.gadgets[this.id].start_closed) { + this.handleToggle(); + } + else if (chrome) { + // set the gadget box width, and remember that we always render as open + chrome.style.width = (my.gadgets[this.id].open_width || 600) + 'px'; + } +}; + +ProfilesGadget.prototype.getIframeUrl = function() { + var url = this.originalGetIframeUrl(); + return url.replace('REPLACE_THIS_VIEW', my.gadgets[this.id].view); +}; + +ProfilesGadget.prototype.handleToggle = function() { + var gadgetIframe = document.getElementById(this.getIframeId()); + if (gadgetIframe) { + var gadgetContent = gadgetIframe.parentNode; + var gadgetImg = document.getElementById('gadgets-gadget-title-image-' + this.id); + if (gadgetContent.style.display) { + //OPEN + gadgetContent.parentNode.style.width = (my.gadgets[this.id].open_width || 600) + 'px'; + gadgetContent.style.display = ''; + gadgetImg.src = '/' + location.pathname.split('/')[1] + '/themes/opensocial/images/openSocial/icon_squareDownArrow.gif'; + // refresh if certain features require so + //if (this.hasFeature('dynamic-height')) { + if (my.gadgets[this.id].chrome_id == 'gadgets-search') { + this.refresh(); + document.getElementById(this.getIframeId()).contentWindow.location.reload(true); + } + + if (my.gadgets[this.id].view == 'home') { + // record in google analytics + _gaq.push(['_trackEvent', my.gadgets[this.id].name, 'OPEN_IN_EDIT', 'profile_edit_view']); + } + else { + osapi.activities.create( + { 'userId': gadgets.util.getUrlParameters()['Person'], + 'appId': my.gadgets[this.id].appId, + 'activity': {'postedTime': new Date().getTime(), 'title': 'gadget viewed', 'body': my.gadgets[this.id].name + ' gadget was viewed' } + }).execute(function(response){}); + // record in google analytics + _gaq.push(['_trackEvent', my.gadgets[this.id].name, 'OPEN']); + } + } + else { + //CLOSE + gadgetContent.parentNode.style.width = (my.gadgets[this.id].closed_width || 600) + 'px'; + gadgetContent.style.display = 'none'; + gadgetImg.src = '/' + location.pathname.split('/')[1] + '/themes/opensocial/images/openSocial/icon_squareArrow.gif'; + if (my.gadgets[this.id].view == 'home') { + // record in google analytics + _gaq.push(['_trackEvent', my.gadgets[this.id].name, 'CLOSE_IN_EDIT', 'profile_edit_view']); + } + else { + // record in google analytics + _gaq.push(['_trackEvent', my.gadgets[this.id].name, 'CLOSE']); + } + } + } +}; + +ProfilesGadget.prototype.getTitleBarContent = function(continuation) { + if (my.gadgets[this.id].view == 'canvas') { + document.getElementById("gadgets-title").innerHTML = (this.title ? this.title : 'Gadget'); + continuation(''); + } + else { + continuation( + ''); + } +}; + +// ProfilesLayoutManager. Creates a FloatLeftLayoutManager for every chromeId in our gadgets +ProfilesLayoutManager = function() { + shindig.LayoutManager.call(this); + // find out what chromeId's are being used, create a FloatLeftLayoutManager for each + this.layoutManagers = {}; + for (var moduleId = 0; moduleId < my.gadgets.length; moduleId++) { + var chromeId = my.gadgets[moduleId].chrome_id; + if (!this.layoutManagers[chromeId]) { + this.layoutManagers[chromeId] = new shindig.FloatLeftLayoutManager(chromeId); + } + } +}; + +ProfilesLayoutManager.inherits(shindig.LayoutManager); + +ProfilesLayoutManager.prototype.getGadgetChrome = function(gadget) { + return this.layoutManagers[my.gadgets[gadget.id].chrome_id].getGadgetChrome(gadget); +}; \ No newline at end of file