diff --git a/assembly/pom.xml b/assembly/pom.xml
index 9e3384036..89929770b 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -22,7 +22,7 @@
org.apache.shindigshindig-project
- 2.0.1-SNAPSHOT
+ 2.0.1.3../pom.xml
diff --git a/extras/pom.xml b/extras/pom.xml
index 8d99f9fc3..ff03ea745 100644
--- a/extras/pom.xml
+++ b/extras/pom.xml
@@ -22,7 +22,7 @@
org.apache.shindigshindig-project
- 2.0.1-SNAPSHOT
+ 2.0.1.3../pom.xml
diff --git a/features/pom.xml b/features/pom.xml
index 5149f6030..51ad9c31e 100644
--- a/features/pom.xml
+++ b/features/pom.xml
@@ -23,7 +23,7 @@
org.apache.shindigshindig-project
- 2.0.1-SNAPSHOT
+ 2.0.1.3../pom.xml
diff --git a/features/src/main/javascript/features/core.json/json.js b/features/src/main/javascript/features/core.json/json.js
index 41544e3b7..996d738cc 100644
--- a/features/src/main/javascript/features/core.json/json.js
+++ b/features/src/main/javascript/features/core.json/json.js
@@ -175,7 +175,6 @@ if (window.JSON && window.JSON.parse && window.JSON.stringify) {
// Join all of the member texts together and wrap them in braces.
return '{' + a.join(',') + '}';
}
- return "undefined";
}
return {
diff --git a/features/src/main/javascript/features/rpc/blank.transport.js b/features/src/main/javascript/features/rpc/blank.transport.js
new file mode 100644
index 000000000..0433e0303
--- /dev/null
+++ b/features/src/main/javascript/features/rpc/blank.transport.js
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+gadgets.rpctx = gadgets.rpctx || {};
+
+if (!gadgets.rpctx.blank) { // make lib resilient to double-inclusion
+
+gadgets.rpctx.blank = function() {
+ return {
+ getCode: function() {
+ return 'blank';
+ },
+
+ isParentVerifiable: function() {
+ return true;
+ },
+
+ init: function(processFn, readyFn) {
+ readyFn('..', true); // Immediately ready to send to parent.
+ return true;
+ },
+
+ setup: function(receiverId, token, forceSecure) {
+ return true;
+ },
+
+ call: function(targetId, from, rpc) {
+ // should never happen
+ }
+ };
+}();
+
+} // !end of double-inclusion guard
diff --git a/features/src/main/javascript/features/rpc/feature.xml b/features/src/main/javascript/features/rpc/feature.xml
index a5f7fe77c..3a81151f1 100644
--- a/features/src/main/javascript/features/rpc/feature.xml
+++ b/features/src/main/javascript/features/rpc/feature.xml
@@ -36,7 +36,7 @@ useLegacyProtocol: Boolean
-
+
@@ -44,7 +44,7 @@ useLegacyProtocol: Boolean
-
+
diff --git a/features/src/main/javascript/features/rpc/nix.transport.js b/features/src/main/javascript/features/rpc/nix.transport.js
deleted file mode 100644
index 85d1b104e..000000000
--- a/features/src/main/javascript/features/rpc/nix.transport.js
+++ /dev/null
@@ -1,342 +0,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.
- */
-
-gadgets.rpctx = gadgets.rpctx || {};
-
-/**
- * For Internet Explorer before version 8, the security model allows anyone
- * parent to set the value of the "opener" property on another window,
- * with only the receiving window able to read it.
- * This method is dubbed "Native IE XDC" (NIX).
- *
- * This method works by placing a handler object in the "opener" property
- * of a gadget when the container sets up the authentication information
- * for that gadget (by calling setAuthToken(...)). At that point, a NIX
- * wrapper is created and placed into the gadget by calling
- * theframe.contentWindow.opener = wrapper. Note that as a result, NIX can
- * only be used by a container to call a particular gadget *after* that
- * gadget has called the container at least once via NIX.
- *
- * The NIX wrappers in this RPC implementation are instances of a VBScript
- * class that is created when this implementation loads. The reason for
- * using a VBScript class stems from the fact that any object can be passed
- * into the opener property.
- * While this is a good thing, as it lets us pass functions and setup a true
- * bidirectional channel via callbacks, it opens a potential security hole
- * by which the other page can get ahold of the "window" or "document"
- * objects in the parent page and in turn wreak havok. This is due to the
- * fact that any JS object useful for establishing such a bidirectional
- * channel (such as a function) can be used to access a function
- * (eg. obj.toString, or a function itself) created in a specific context,
- * in particular the global context of the sender. Suppose container
- * domain C passes object obj to gadget on domain G. Then the gadget can
- * access C's global context using:
- * var parentWindow = (new obj.toString.constructor("return window;"))();
- * Nulling out all of obj's properties doesn't fix this, since IE helpfully
- * restores them to their original values if you do something like:
- * delete obj.toString; delete obj.toString;
- * Thus, we wrap the necessary functions and information inside a VBScript
- * object. VBScript objects in IE, like DOM objects, are in fact COM
- * wrappers when used in JavaScript, so we can safely pass them around
- * without worrying about a breach of context while at the same time
- * allowing them to act as a pass-through mechanism for information
- * and function calls. The implementation details of this VBScript wrapper
- * can be found in the setupChannel() method below.
- *
- * nix: Internet Explorer-specific window.opener trick.
- * - Internet Explorer 6
- * - Internet Explorer 7
- */
-if (!gadgets.rpctx.nix) { // make lib resilient to double-inclusion
-
-gadgets.rpctx.nix = function() {
- // Consts for NIX. VBScript doesn't
- // allow items to start with _ for some reason,
- // so we need to make these names quite unique, as
- // they will go into the global namespace.
- var NIX_WRAPPER = 'GRPC____NIXVBS_wrapper';
- var NIX_GET_WRAPPER = 'GRPC____NIXVBS_get_wrapper';
- var NIX_HANDLE_MESSAGE = 'GRPC____NIXVBS_handle_message';
- var NIX_CREATE_CHANNEL = 'GRPC____NIXVBS_create_channel';
- var MAX_NIX_SEARCHES = 10;
- var NIX_SEARCH_PERIOD = 500;
-
- // JavaScript reference to the NIX VBScript wrappers.
- // Gadgets will have but a single channel under
- // nix_channels['..'] while containers will have a channel
- // per gadget stored under the gadget's ID.
- var nix_channels = {};
- var isForceSecure = {};
-
- // Store the ready signal method for use on handshake complete.
- var ready;
- var numHandlerSearches = 0;
-
- // Search for NIX handler to parent. Tries MAX_NIX_SEARCHES times every
- // NIX_SEARCH_PERIOD milliseconds.
- function conductHandlerSearch() {
- // Call from gadget to the container.
- var handler = nix_channels['..'];
- if (handler) {
- return;
- }
-
- if (++numHandlerSearches > MAX_NIX_SEARCHES) {
- // Handshake failed. Will fall back.
- gadgets.warn('Nix transport setup failed, falling back...');
- ready('..', false);
- return;
- }
-
- // If the gadget has yet to retrieve a reference to
- // the NIX handler, try to do so now. We don't do a
- // typeof(window.opener.GetAuthToken) check here
- // because it means accessing that field on the COM object, which,
- // being an internal function reference, is not allowed.
- // "in" works because it merely checks for the prescence of
- // the key, rather than actually accessing the object's property.
- // This is just a sanity check, not a validity check.
- if (!handler && window.opener && "GetAuthToken" in window.opener) {
- handler = window.opener;
-
- // Create the channel to the parent/container.
- // First verify that it knows our auth token to ensure it's not
- // an impostor.
- if (handler.GetAuthToken() == gadgets.rpc.getAuthToken('..')) {
- // Auth match - pass it back along with our wrapper to finish.
- // own wrapper and our authentication token for co-verification.
- var token = gadgets.rpc.getAuthToken('..');
- handler.CreateChannel(window[NIX_GET_WRAPPER]('..', token),
- token);
- // Set channel handler
- nix_channels['..'] = handler;
- window.opener = null;
-
- // Signal success and readiness to send to parent.
- // Container-to-gadget bit flipped in CreateChannel.
- ready('..', true);
- return;
- }
- }
-
- // Try again.
- window.setTimeout(function() { conductHandlerSearch(); },
- NIX_SEARCH_PERIOD);
- }
-
- // Returns current window location, without hash values
- function getLocationNoHash() {
- var loc = window.location.href;
- var idx = loc.indexOf('#');
- if (idx == -1) {
- return loc;
- }
- return loc.substring(0, idx);
- }
-
- // When "forcesecure" is set to true, use the relay file and a simple variant of IFPC to first
- // authenticate the container and gadget with each other. Once that is done, then initialize
- // the NIX protocol.
- function setupSecureRelayToParent(rpctoken) {
- // To the parent, transmit the child's URL, the passed in auth
- // token, and another token generated by the child.
- var childToken = (0x7FFFFFFF * Math.random()) | 0; // TODO expose way to have child set this value
- var data = [
- getLocationNoHash(),
- childToken
- ];
- gadgets.rpc._createRelayIframe(rpctoken, data);
-
- // listen for response from parent
- var hash = window.location.href.split('#')[1] || '';
-
- function relayTimer() {
- var newHash = window.location.href.split('#')[1] || '';
- if (newHash !== hash) {
- clearInterval(relayTimerId);
- var params = gadgets.util.getUrlParameters(window.location.href);
- if (params.childtoken == childToken) {
- // parent has been authenticated; now init NIX
- conductHandlerSearch();
- return;
- }
- // security error -- token didn't match
- ready('..', false);
- }
- }
- var relayTimerId = setInterval( relayTimer, 100 );
- }
-
- return {
- getCode: function() {
- return 'nix';
- },
-
- isParentVerifiable: function(opt_receiverId) {
- // NIX is only parent verifiable if a receiver was setup with "forcesecure" set to TRUE.
- if (opt_receiverId) {
- return isForceSecure[opt_receiverId];
- }
- return false;
- },
-
- init: function(processFn, readyFn) {
- ready = readyFn;
-
- // Ensure VBScript wrapper code is in the page and that the
- // global Javascript handlers have been set.
- // VBScript methods return a type of 'unknown' when
- // checked via the typeof operator in IE. Fortunately
- // for us, this only applies to COM objects, so we
- // won't see this for a real Javascript object.
- if (typeof window[NIX_GET_WRAPPER] !== 'unknown') {
- window[NIX_HANDLE_MESSAGE] = function(data) {
- window.setTimeout(
- function() { processFn(gadgets.json.parse(data)); }, 0);
- };
-
- window[NIX_CREATE_CHANNEL] = function(name, channel, token) {
- // Verify the authentication token of the gadget trying
- // to create a channel for us.
- if (gadgets.rpc.getAuthToken(name) === token) {
- nix_channels[name] = channel;
- ready(name, true);
- }
- };
-
- // Inject the VBScript code needed.
- var vbscript =
- // We create a class to act as a wrapper for
- // a Javascript call, to prevent a break in of
- // the context.
- 'Class ' + NIX_WRAPPER + '\n '
-
- // An internal member for keeping track of the
- // name of the document (container or gadget)
- // for which this wrapper is intended. For
- // those wrappers created by gadgets, this is not
- // used (although it is set to "..")
- + 'Private m_Intended\n'
-
- // Stores the auth token used to communicate with
- // the gadget. The GetChannelCreator method returns
- // an object that returns this auth token. Upon matching
- // that with its own, the gadget uses the object
- // to actually establish the communication channel.
- + 'Private m_Auth\n'
-
- // Method for internally setting the value
- // of the m_Intended property.
- + 'Public Sub SetIntendedName(name)\n '
- + 'If isEmpty(m_Intended) Then\n'
- + 'm_Intended = name\n'
- + 'End If\n'
- + 'End Sub\n'
-
- // Method for internally setting the value of the m_Auth property.
- + 'Public Sub SetAuth(auth)\n '
- + 'If isEmpty(m_Auth) Then\n'
- + 'm_Auth = auth\n'
- + 'End If\n'
- + 'End Sub\n'
-
- // A wrapper method which actually causes a
- // message to be sent to the other context.
- + 'Public Sub SendMessage(data)\n '
- + NIX_HANDLE_MESSAGE + '(data)\n'
- + 'End Sub\n'
-
- // Returns the auth token to the gadget, so it can
- // confirm a match before initiating the connection
- + 'Public Function GetAuthToken()\n '
- + 'GetAuthToken = m_Auth\n'
- + 'End Function\n'
-
- // Method for setting up the container->gadget
- // channel. Not strictly needed in the gadget's
- // wrapper, but no reason to get rid of it. Note here
- // that we pass the intended name to the NIX_CREATE_CHANNEL
- // method so that it can save the channel in the proper place
- // *and* verify the channel via the authentication token passed
- // here.
- + 'Public Sub CreateChannel(channel, auth)\n '
- + 'Call ' + NIX_CREATE_CHANNEL + '(m_Intended, channel, auth)\n'
- + 'End Sub\n'
- + 'End Class\n'
-
- // Function to get a reference to the wrapper.
- + 'Function ' + NIX_GET_WRAPPER + '(name, auth)\n'
- + 'Dim wrap\n'
- + 'Set wrap = New ' + NIX_WRAPPER + '\n'
- + 'wrap.SetIntendedName name\n'
- + 'wrap.SetAuth auth\n'
- + 'Set ' + NIX_GET_WRAPPER + ' = wrap\n'
- + 'End Function';
-
- try {
- window.execScript(vbscript, 'vbscript');
- } catch (e) {
- return false;
- }
- }
- return true;
- },
-
- setup: function(receiverId, token, forcesecure) {
- isForceSecure[receiverId] = !!forcesecure;
- if (receiverId === '..') {
- if (forcesecure) {
- setupSecureRelayToParent(token);
- } else {
- conductHandlerSearch();
- }
- return true;
- }
- try {
- var frame = document.getElementById(receiverId);
- var wrapper = window[NIX_GET_WRAPPER](receiverId, token);
- frame.contentWindow.opener = wrapper;
- } catch (e) {
- return false;
- }
- return true;
- },
-
- call: function(targetId, from, rpc) {
- try {
- // If we have a handler, call it.
- if (nix_channels[targetId]) {
- nix_channels[targetId].SendMessage(gadgets.json.stringify(rpc));
- }
- } catch (e) {
- return false;
- }
- return true;
- },
-
- // data = [child URL, child auth token]
- relayOnload: function(receiverId, data) {
- // transmit childtoken back to child to complete authentication
- var src = data[0] + '#childtoken=' + data[1];
- var childIframe = document.getElementById(receiverId);
- childIframe.src = src;
- }
- };
-}();
-
-} // !end of double-inclusion guard
diff --git a/features/src/main/javascript/features/rpc/rpc.js b/features/src/main/javascript/features/rpc/rpc.js
index c39c95ef8..261a6e503 100644
--- a/features/src/main/javascript/features/rpc/rpc.js
+++ b/features/src/main/javascript/features/rpc/rpc.js
@@ -167,9 +167,16 @@ gadgets.rpc = function() {
* @member gadgets.rpc
*/
function getTransport() {
+ var targetEl = getTargetWin('..');
+ try {
+ // If this succeeds, then same-domain policy applied
+ var t = targetEl.gadgets.rpc.receiveSameDomain;
+ return gadgets.rpctx.blank;
+ } catch (e) { }
+
return typeof window.postMessage === 'function' ? gadgets.rpctx.wpm :
typeof window.postMessage === 'object' ? gadgets.rpctx.wpm :
- window.ActiveXObject ? gadgets.rpctx.nix :
+ window.ActiveXObject ? gadgets.rpctx.ifpc:
navigator.userAgent.indexOf('WebKit') > 0 ? gadgets.rpctx.rmr :
navigator.product === 'Gecko' ? gadgets.rpctx.frameElement :
gadgets.rpctx.ifpc;
@@ -408,11 +415,8 @@ gadgets.rpc = function() {
* of the channel once they send their first messages.
*/
function setupFrame(frameId, token, forcesecure) {
- if (setup[frameId] === true) {
- return;
- }
- if (typeof setup[frameId] === 'undefined') {
+ if (typeof setup[frameId] === 'undefined') {
setup[frameId] = 0;
}
diff --git a/java/common/pom.xml b/java/common/pom.xml
index 6bbcc6e29..a34e5e104 100644
--- a/java/common/pom.xml
+++ b/java/common/pom.xml
@@ -22,7 +22,7 @@
org.apache.shindigshindig-project
- 2.0.1-SNAPSHOT
+ 2.0.1.3../../pom.xml
diff --git a/java/gadgets/pom.xml b/java/gadgets/pom.xml
index 5f7b29073..5cedfb6a6 100644
--- a/java/gadgets/pom.xml
+++ b/java/gadgets/pom.xml
@@ -22,7 +22,7 @@
org.apache.shindigshindig-project
- 2.0.1-SNAPSHOT
+ 2.0.1.3../../pom.xml
diff --git a/java/gadgets/src/main/java/org/apache/shindig/gadgets/FeedProcessor.java b/java/gadgets/src/main/java/org/apache/shindig/gadgets/FeedProcessor.java
index 6db45bfc7..1c2e4d71c 100644
--- a/java/gadgets/src/main/java/org/apache/shindig/gadgets/FeedProcessor.java
+++ b/java/gadgets/src/main/java/org/apache/shindig/gadgets/FeedProcessor.java
@@ -30,6 +30,7 @@
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
+import com.sun.syndication.feed.synd.SyndImage;
import com.sun.syndication.feed.synd.SyndPerson;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
@@ -38,10 +39,14 @@
import org.json.JSONException;
import org.json.JSONObject;
+import com.google.inject.ImplementedBy;
+
+
/**
* Processes RSS & Atom Feeds and converts them into JSON output.
*/
-public class FeedProcessor {
+@ImplementedBy(FeedProcessorImpl.class)
+public interface FeedProcessor {
/**
* Converts feed XML to JSON.
@@ -56,131 +61,6 @@ public class FeedProcessor {
* Number of entries to return.
* @return The JSON representation of the feed.
*/
- @SuppressWarnings("unchecked")
- public JSONObject process(String feedUrl, String feedXml, boolean getSummaries, int numEntries)
- throws GadgetException {
- try {
- SyndFeed feed = new SyndFeedInput().build(new StringReader(feedXml));
- JSONObject json = new JSONObject();
- json.put("Title", feed.getTitle());
- json.put("URL", feedUrl);
- json.put("Description", feed.getDescription());
- json.put("Link", feed.getLink());
-
- List authors = feed.getAuthors();
- String jsonAuthor = null;
- if (authors != null && !authors.isEmpty()) {
- SyndPerson author = authors.get(0);
- if (author.getName() != null) {
- jsonAuthor = author.getName();
- } else if (author.getEmail() != null) {
- jsonAuthor = author.getEmail();
- }
- }
- JSONArray entries = new JSONArray();
- json.put("Entry", entries);
-
- int entryCnt = 0;
- for (Object obj : feed.getEntries()) {
- SyndEntry e = (SyndEntry) obj;
- if (entryCnt >= numEntries) {
- break;
- }
- entryCnt++;
-
- JSONObject entry = new JSONObject();
- entry.put("Title", e.getTitle());
- entry.put("Link", e.getLink());
- if (getSummaries) {
- if (e.getContents() != null && !e.getContents().isEmpty()) {
- entry.put("Summary", ((SyndContent) e.getContents().get(0)).getValue());
- } else {
- entry.put("Summary", e.getDescription() != null ? e.getDescription().getValue() : "");
- }
- }
-
- if (e.getUpdatedDate() != null) {
- entry.put("Date", e.getUpdatedDate().getTime());
- } else if (e.getPublishedDate() != null) {
- entry.put("Date", e.getPublishedDate().getTime());
- } else {
- entry.put("Date", 0);
- }
-
- // if no author at feed level, use the first entry author
- if (jsonAuthor == null) {
- jsonAuthor = e.getAuthor();
- }
-
- JSONObject media = new JSONObject();
- MediaEntryModule mediaModule = (MediaEntryModule) e.getModule(MediaModule.URI);
- if (mediaModule != null) {
- if (mediaModule.getMediaContents().length > 0) {
- JSONArray contents = new JSONArray();
-
- for (MediaContent c : mediaModule.getMediaContents()) {
- JSONObject content = new JSONObject();
-
- if (c.getReference() instanceof UrlReference) {
- content.put("URL", ((UrlReference) c.getReference()).getUrl().toString());
- }
-
- if (c.getType() != null) {
- content.put("Type", c.getType());
- }
-
- if (c.getWidth() != null) {
- content.put("Width", c.getWidth());
- }
-
- if (c.getHeight() != null) {
- content.put("Height", c.getHeight());
- }
-
- contents.put(content);
- }
-
- media.put("Contents", contents);
- }
-
- if (mediaModule.getMetadata() != null) {
- if (mediaModule.getMetadata().getThumbnail().length > 0) {
- // "If multiple thumbnails are included, it is assumed that they are in order of importance"
- // Only use the first thumbnail for simplicity's
- // sake
-
- JSONObject thumbnail = new JSONObject();
-
- Thumbnail t = mediaModule.getMetadata().getThumbnail()[0];
- thumbnail.put("URL", t.getUrl().toString());
-
- if (t.getWidth() != null) {
- thumbnail.put("Width", t.getWidth());
- }
-
- if (t.getHeight() != null) {
- thumbnail.put("Height", t.getHeight());
- }
-
- media.put("Thumbnail", thumbnail);
- }
- }
- }
-
- entry.put("Media", media);
-
- entries.put(entry);
- }
-
- json.put("Author", (jsonAuthor != null) ? jsonAuthor : "");
- return json;
- } catch (JSONException e) {
- // This shouldn't ever happen.
- throw new RuntimeException(e);
- } catch (FeedException e) {
- throw new GadgetException(GadgetException.Code.MALFORMED_XML_DOCUMENT, e, HttpResponse.SC_BAD_GATEWAY);
- } catch (IllegalArgumentException e) {
- throw new GadgetException(GadgetException.Code.MALFORMED_XML_DOCUMENT, e, HttpResponse.SC_BAD_GATEWAY);
- }
- }
+ JSONObject process(String feedUrl, String feedXml, boolean getSummaries, int numEntries)
+ throws GadgetException;
}
diff --git a/java/gadgets/src/main/java/org/apache/shindig/gadgets/FeedProcessorImpl.java b/java/gadgets/src/main/java/org/apache/shindig/gadgets/FeedProcessorImpl.java
new file mode 100644
index 000000000..8035340ba
--- /dev/null
+++ b/java/gadgets/src/main/java/org/apache/shindig/gadgets/FeedProcessorImpl.java
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+package org.apache.shindig.gadgets;
+
+import java.io.StringReader;
+import java.util.List;
+
+import com.sun.syndication.feed.module.mediarss.types.UrlReference;
+
+import com.sun.syndication.feed.module.mediarss.MediaEntryModule;
+import com.sun.syndication.feed.module.mediarss.MediaModule;
+import com.sun.syndication.feed.module.mediarss.types.MediaContent;
+import com.sun.syndication.feed.module.mediarss.types.Thumbnail;
+import com.sun.syndication.feed.synd.SyndContent;
+import com.sun.syndication.feed.synd.SyndEntry;
+import com.sun.syndication.feed.synd.SyndFeed;
+import com.sun.syndication.feed.synd.SyndImage;
+import com.sun.syndication.feed.synd.SyndPerson;
+import com.sun.syndication.io.FeedException;
+import com.sun.syndication.io.SyndFeedInput;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Processes RSS & Atom Feeds and converts them into JSON output.
+ */
+public class FeedProcessorImpl implements FeedProcessor {
+
+ /**
+ * Converts feed XML to JSON.
+ *
+ * @param feedUrl
+ * The url that the feed was retrieved from.
+ * @param feedXml
+ * The raw XML of the feed to be converted.
+ * @param getSummaries
+ * True if summaries should be returned.
+ * @param numEntries
+ * Number of entries to return.
+ * @return The JSON representation of the feed.
+ */
+ @SuppressWarnings("unchecked")
+ public JSONObject process(String feedUrl, String feedXml, boolean getSummaries, int numEntries)
+ throws GadgetException {
+ try {
+ SyndFeed feed = new SyndFeedInput().build(new StringReader(feedXml));
+ JSONObject json = new JSONObject();
+ json.put("Title", feed.getTitle());
+ json.put("URL", feedUrl);
+ json.put("Description", feed.getDescription());
+ json.put("Link", feed.getLink());
+
+ //Retrieve the feed image if it is available as well as an image url if the image is available.
+ if(feed.getImage() != null && !feed.getImage().getUrl().isEmpty()){
+ SyndImage feedImage = (SyndImage)feed.getImage();
+ JSONObject jsonImage = new JSONObject();
+ jsonImage.put("Url", feedImage.getUrl());
+ if(feedImage.getTitle() != null
+ && !feedImage.getTitle().isEmpty()){
+ jsonImage.put("Title", feedImage.getTitle());
+ }
+ if(feedImage.getDescription() != null &&
+ !feedImage.getDescription().isEmpty()){
+ jsonImage.put("Description", feedImage.getDescription());
+ }
+ if(feedImage.getLink() != null &&
+ !feedImage.getLink().isEmpty()){
+ jsonImage.put("Link", feedImage.getLink());
+ }
+ json.put("Image", jsonImage);
+ }
+
+
+ List authors = feed.getAuthors();
+ String jsonAuthor = null;
+ if (authors != null && !authors.isEmpty()) {
+ SyndPerson author = authors.get(0);
+ if (author.getName() != null) {
+ jsonAuthor = author.getName();
+ } else if (author.getEmail() != null) {
+ jsonAuthor = author.getEmail();
+ }
+ }
+ JSONArray entries = new JSONArray();
+ json.put("Entry", entries);
+
+ int entryCnt = 0;
+ for (Object obj : feed.getEntries()) {
+ SyndEntry e = (SyndEntry) obj;
+ if (entryCnt >= numEntries) {
+ break;
+ }
+ entryCnt++;
+
+ JSONObject entry = new JSONObject();
+ entry.put("Title", e.getTitle());
+ entry.put("Link", e.getLink());
+ if (getSummaries) {
+ if (e.getContents() != null && !e.getContents().isEmpty()) {
+ entry.put("Summary", ((SyndContent) e.getContents().get(0)).getValue());
+ } else {
+ entry.put("Summary", e.getDescription() != null ? e.getDescription().getValue() : "");
+ }
+ }
+
+ if (e.getUpdatedDate() != null) {
+ entry.put("Date", e.getUpdatedDate().getTime());
+ } else if (e.getPublishedDate() != null) {
+ entry.put("Date", e.getPublishedDate().getTime());
+ } else {
+ entry.put("Date", 0);
+ }
+
+ // if no author at feed level, use the first entry author
+ if (jsonAuthor == null) {
+ jsonAuthor = e.getAuthor();
+ }
+
+ JSONObject media = new JSONObject();
+ MediaEntryModule mediaModule = (MediaEntryModule) e.getModule(MediaModule.URI);
+ if (mediaModule != null) {
+ if (mediaModule.getMediaContents().length > 0) {
+ JSONArray contents = new JSONArray();
+
+ for (MediaContent c : mediaModule.getMediaContents()) {
+ JSONObject content = new JSONObject();
+
+ if (c.getReference() instanceof UrlReference) {
+ content.put("URL", ((UrlReference) c.getReference()).getUrl().toString());
+ }
+
+ if (c.getType() != null) {
+ content.put("Type", c.getType());
+ }
+
+ if (c.getWidth() != null) {
+ content.put("Width", c.getWidth());
+ }
+
+ if (c.getHeight() != null) {
+ content.put("Height", c.getHeight());
+ }
+
+ contents.put(content);
+ }
+
+ media.put("Contents", contents);
+ }
+
+ if (mediaModule.getMetadata() != null) {
+ if (mediaModule.getMetadata().getThumbnail().length > 0) {
+ // "If multiple thumbnails are included, it is assumed that they are in order of importance"
+ // Only use the first thumbnail for simplicity's
+ // sake
+
+ JSONObject thumbnail = new JSONObject();
+
+ Thumbnail t = mediaModule.getMetadata().getThumbnail()[0];
+ thumbnail.put("URL", t.getUrl().toString());
+
+ if (t.getWidth() != null) {
+ thumbnail.put("Width", t.getWidth());
+ }
+
+ if (t.getHeight() != null) {
+ thumbnail.put("Height", t.getHeight());
+ }
+
+ media.put("Thumbnail", thumbnail);
+ }
+ }
+ }
+
+ entry.put("Media", media);
+
+ entries.put(entry);
+ }
+
+ json.put("Author", (jsonAuthor != null) ? jsonAuthor : "");
+ return json;
+ } catch (JSONException e) {
+ // This shouldn't ever happen.
+ throw new RuntimeException(e);
+ } catch (FeedException e) {
+ throw new GadgetException(GadgetException.Code.MALFORMED_XML_DOCUMENT, e, HttpResponse.SC_BAD_GATEWAY);
+ } catch (IllegalArgumentException e) {
+ throw new GadgetException(GadgetException.Code.MALFORMED_XML_DOCUMENT, e, HttpResponse.SC_BAD_GATEWAY);
+ }
+ }
+}
diff --git a/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java b/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java
index f6a33ea7f..0c5c3860f 100644
--- a/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java
+++ b/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpRequestHandler.java
@@ -48,6 +48,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
+import com.google.inject.Provider;
/**
* An alternate implementation of the Http proxy service using the standard API dispatcher for REST
@@ -96,12 +97,15 @@ public class HttpRequestHandler {
private final RequestPipeline requestPipeline;
private final ResponseRewriterRegistry contentRewriterRegistry;
+ private final Provider feedProcessorProvider;
@Inject
public HttpRequestHandler(RequestPipeline requestPipeline,
- ResponseRewriterRegistry contentRewriterRegistry) {
+ ResponseRewriterRegistry contentRewriterRegistry,
+ Provider feedProcessorProvider) {
this.requestPipeline = requestPipeline;
this.contentRewriterRegistry = contentRewriterRegistry;
+ this.feedProcessorProvider = feedProcessorProvider;
}
@@ -298,7 +302,7 @@ protected Object transformBody(HttpApiRequest request, HttpResponse results)
/** Processes a feed (RSS or Atom) using FeedProcessor. */
protected Object processFeed(HttpApiRequest req, String responseBody)
throws GadgetException {
- return new FeedProcessor().process(req.href.toString(), responseBody, req.summarize,
+ return feedProcessorProvider.get().process(req.href.toString(), responseBody, req.summarize,
req.entryCount);
}
diff --git a/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java b/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java
index 3e470875c..bf859234a 100644
--- a/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java
+++ b/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/JsonRpcHandler.java
@@ -218,6 +218,7 @@ protected JSONObject getGadgetJson(Gadget gadget, GadgetSpec spec)
.put("moduleId", context.getModuleId())
.put("title", prefs.getTitle())
.put("titleUrl", prefs.getTitleUrl().toString())
+ .put("description", prefs.getDescription())
.put("views", views)
.put("features", features)
.put("featureDetails", featureDetailList)
diff --git a/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java b/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
index 153a6e42e..fb3bbc1bc 100644
--- a/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
+++ b/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
@@ -19,6 +19,7 @@
package org.apache.shindig.gadgets.servlet;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.commons.lang.StringUtils;
@@ -72,12 +73,15 @@ public class MakeRequestHandler {
private final RequestPipeline requestPipeline;
private final ResponseRewriterRegistry contentRewriterRegistry;
+ private final Provider feedProcessorProvider;
@Inject
public MakeRequestHandler(RequestPipeline requestPipeline,
- ResponseRewriterRegistry contentRewriterRegistry) {
+ ResponseRewriterRegistry contentRewriterRegistry,
+ Provider feedProcessorProvider) {
this.requestPipeline = requestPipeline;
this.contentRewriterRegistry = contentRewriterRegistry;
+ this.feedProcessorProvider = feedProcessorProvider;
}
/**
@@ -273,7 +277,7 @@ private String processFeed(String url, HttpServletRequest req, String xml)
throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
"numEntries paramater is not a number", HttpResponse.SC_BAD_REQUEST);
}
- return new FeedProcessor().process(url, xml, getSummaries, numEntries).toString();
+ return feedProcessorProvider.get().process(url, xml, getSummaries, numEntries).toString();
}
/**
diff --git a/java/gadgets/src/test/java/org/apache/shindig/gadgets/FeedProcessorTest.java b/java/gadgets/src/test/java/org/apache/shindig/gadgets/FeedProcessorImplTest.java
similarity index 95%
rename from java/gadgets/src/test/java/org/apache/shindig/gadgets/FeedProcessorTest.java
rename to java/gadgets/src/test/java/org/apache/shindig/gadgets/FeedProcessorImplTest.java
index bc839a4d0..7aa9e8b8d 100644
--- a/java/gadgets/src/test/java/org/apache/shindig/gadgets/FeedProcessorTest.java
+++ b/java/gadgets/src/test/java/org/apache/shindig/gadgets/FeedProcessorImplTest.java
@@ -26,13 +26,17 @@
import org.junit.Test;
/**
- * Tests for FeedProcessor
+ * Tests for FeedProcessorImpl
*/
-public class FeedProcessorTest {
+public class FeedProcessorImplTest {
private final static String FEED_TITLE = "Example Feed";
private final static String FEED_AUTHOR = "John Doe";
private final static String FEED_AUTHOR_EMAIL = "john.doe@example.com";
+ private final static String FEED_IMAGE_URL = "http://example.org/example.gif";
+ private final static String FEED_IMAGE_TITLE = "Example Feed Image";
+ private final static String FEED_IMAGE_DESCRIPTION = "Example Feed Image Description";
+ private final static String FEED_IMAGE_LINK = "http://example.org/";
private final static String FEED_ENTRY_TITLE = "Atom-Powered Robots Run Amok";
private final static String FEED_ENTRY_LINK = "http://example.org/2003/12/13/entry03";
private final static String FEED_ENTRY_SUMMARY = "Some text.";
@@ -46,6 +50,12 @@ public class FeedProcessorTest {
"http://example.org/" +
"Example RSS Feed" +
"Sun, 19 May 2002 15:21:36 GMT" +
+ "" +
+ "" + FEED_IMAGE_URL + "" +
+ "" + FEED_IMAGE_TITLE + "" +
+ "" + FEED_IMAGE_DESCRIPTION + "" +
+ "" + FEED_IMAGE_LINK + "" +
+ "" +
"" +
"" + FEED_ENTRY_TITLE + "" +
"" + FEED_ENTRY_LINK + "" +
@@ -152,10 +162,10 @@ public class FeedProcessorTest {
private final static String BAD_XML = "broken xml !!!! & ><";
private final static String INVALID_XML = "";
- private final FeedProcessor processor;
+ private final FeedProcessorImpl processor;
- public FeedProcessorTest() {
- processor = new FeedProcessor();
+ public FeedProcessorImplTest() {
+ processor = new FeedProcessorImpl();
}
@Test
diff --git a/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/FakeProcessor.java b/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/FakeProcessor.java
index 5b4108254..879733dcc 100644
--- a/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/FakeProcessor.java
+++ b/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/FakeProcessor.java
@@ -37,13 +37,14 @@ public class FakeProcessor extends Processor {
public static final Uri SPEC_URL2 = Uri.parse("http://example.org/g2.xml");
public static final String SPEC_TITLE = "JSON-TEST";
public static final String SPEC_TITLE2 = "JSON-TEST2";
+ public static final String SPEC_DESCRIPTION = "JSON-DESCRIPTION";
public static final int PREFERRED_HEIGHT = 100;
public static final int PREFERRED_WIDTH = 242;
public static final String LINK_REL = "rel";
public static final String LINK_HREF = "http://example.org/foo";
public static final String SPEC_XML =
"" +
- "" +
+ "" +
" " +
"" +
"" +
diff --git a/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpRequestHandlerTest.java b/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpRequestHandlerTest.java
index b52892580..c7861ae7a 100644
--- a/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpRequestHandlerTest.java
+++ b/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpRequestHandlerTest.java
@@ -32,6 +32,8 @@
import org.apache.shindig.common.testing.FakeGadgetToken;
import org.apache.shindig.common.uri.Uri;
import org.apache.shindig.gadgets.AuthType;
+import org.apache.shindig.gadgets.FeedProcessor;
+import org.apache.shindig.gadgets.FeedProcessorImpl;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
import org.apache.shindig.gadgets.http.HttpResponseBuilder;
@@ -60,6 +62,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.inject.Guice;
import com.google.inject.Injector;
+import com.google.inject.Provider;
/**
* Has coverage for all tests in MakeRequestHandlerTest and should be maintained in sync until
@@ -82,6 +85,12 @@ public class HttpRequestHandlerTest extends EasyMockTestCase {
private final Map emptyFormItems = Collections.emptyMap();
+ private final Provider feedProcessorProvider = new Provider() {
+ public FeedProcessor get() {
+ return new FeedProcessorImpl();
+ }
+ };
+
@Before
public void setUp() throws Exception {
token = new FakeGadgetToken();
@@ -90,7 +99,7 @@ public void setUp() throws Exception {
Injector injector = Guice.createInjector();
converter = new BeanJsonConverter(injector);
- HttpRequestHandler handler = new HttpRequestHandler(pipeline, rewriterRegistry);
+ HttpRequestHandler handler = new HttpRequestHandler(pipeline, rewriterRegistry, feedProcessorProvider);
registry = new DefaultHandlerRegistry(injector, converter,
new HandlerExecutionListener.NoOpHandler());
registry.addHandlers(ImmutableSet.