Commits

Don Brown committed 76742bd

Better error handling, new container

  • Participants
  • Parent commits f2b1aa3

Comments (0)

Files changed (6)

         <atlassian.product.test-lib.version>2.2.2</atlassian.product.test-lib.version>
         <jdkLevel>1.6</jdkLevel>
         <jvmArgs>-Xmx600m -XX:MaxPermSize=256m</jvmArgs>
-        <remotable.plugins.version>0.5.794</remotable.plugins.version>
+        <remotable.plugins.version>0.5.809</remotable.plugins.version>
     </properties>
 
     <dependencyManagement>

File src/main/java/services/FeedRetrievalException.java

+package services;
+
+public class FeedRetrievalException extends RuntimeException
+{
+    public FeedRetrievalException()
+    {
+        super();
+    }
+
+    public FeedRetrievalException(String message)
+    {
+        super(message);
+    }
+
+    public FeedRetrievalException(String message, Throwable cause)
+    {
+        super(message, cause);
+    }
+
+    public FeedRetrievalException(Throwable cause)
+    {
+        super(cause);
+    }
+}

File src/main/java/services/RssService.java

 import com.sun.syndication.io.XmlReader;
 import org.apache.commons.io.IOUtils;
 
+import javax.annotation.Nullable;
 import javax.inject.Singleton;
 import java.io.IOException;
 import java.io.InputStream;
                     {
                         return parseRSSFeed(url, input.getEntityStream());
                     }
-                }).claim();
+                })
+                .others(new Function<Response, SyndFeed>()
+                {
+                    @Override
+                    public SyndFeed apply(Response input)
+                    {
+                        throw new FeedRetrievalException("Unexpected response status: " + input.getStatusCode());
+                    }
+                })
+                .fail(new Function<Throwable, SyndFeed>()
+                {
+                    @Override
+                    public SyndFeed apply(Throwable input)
+                    {
+                        throw new FeedRetrievalException(input);
+                    }
+                })
+                .claim();
     }
 
     private SyndFeed parseRSSFeed(final String url, final InputStream inputStream) throws IllegalArgumentException

File src/main/java/servlets/RssInlineServlet.java

 import com.atlassian.plugin.remotable.api.annotation.ServiceReference;
 import com.atlassian.plugin.remotable.api.service.RenderContext;
 import com.atlassian.templaterenderer.TemplateRenderer;
+import com.google.common.collect.Lists;
 import com.sun.syndication.feed.synd.SyndFeed;
+import org.apache.commons.lang.StringUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import services.FeedRetrievalException;
 import services.RssService;
 
 import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.LinkedList;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 import static com.google.common.collect.Maps.newHashMap;
-import static java.lang.Integer.parseInt;
 
 @Singleton
 public class RssInlineServlet extends HttpServlet
 {
     private static final Logger log = LoggerFactory.getLogger(RssInlineServlet.class);
+    public static final int DEFAULT_MAX = 5;
 
     @Inject
     private RssService rssService;
     @Override
     protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
     {
-        final String rssUrl = req.getParameter("url");
+        final String rssUrl = getRssUrl(req);
+        final Integer maxEntries = getMaxEntries(req);
 
-        String html = retrieveAndRender(rssUrl, req);
-        resp.setContentType("text/xml");
-        resp.setDateHeader("Expires", System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
-        resp.setHeader("Cache-Control", "public");
+        final String html;
+        if (!isValidUrl(rssUrl) || maxEntries < 1)
+        {
+            try
+            {
+                final JSONObject errorObject = new JSONObject();
+                final Collection<JSONObject> fieldErrors = new LinkedList<JSONObject>();
+                if (!isValidUrl(rssUrl))
+                {
+                    fieldErrors.add(newFieldError("url", "must be a valid URL"));
+                }
+                if (maxEntries < 1)
+                {
+                    fieldErrors.add(newFieldError("max", "must be 1 or greater"));
+                }
+                if (!fieldErrors.isEmpty())
+                {
+                    errorObject.put("fields", new JSONArray(fieldErrors));
+                }
+                html = errorObject.toString();
+                resp.setContentType("application/json");
+                resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            }
+            catch (JSONException e)
+            {
+                throw new ServletException(e);
+            }
+        }
+        else
+        {
+            html = retrieveAndRender(rssUrl, req);
+            resp.setContentType("text/xml");
+            resp.setDateHeader("Expires", System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+            resp.setHeader("Cache-Control", "public");
+        }
 
-        byte[] data = html.getBytes(Charset.forName("UTF-8"));
+        final byte[] data = html.getBytes(Charset.forName("UTF-8"));
         resp.setContentLength(data.length);
         resp.getOutputStream().write(data);
         resp.getOutputStream().close();
     }
 
+    private boolean isValidUrl(String rssUrl)
+    {
+        try
+        {
+            new URI(rssUrl);
+            return true;
+        }
+        catch (URISyntaxException ignore)
+        {
+            return false;
+        }
+    }
+
+    private JSONObject newFieldError(String name, String message) throws JSONException
+    {
+        final JSONObject fieldError = new JSONObject();
+        fieldError.put("name", name);
+        fieldError.put("message", message);
+        return fieldError;
+    }
+
+    private Integer getMaxEntries(HttpServletRequest req)
+    {
+        String macroMax = req.getParameter("max");
+        return macroMax != null ? Integer.valueOf(macroMax) : DEFAULT_MAX;
+    }
+
+    private String getRssUrl(HttpServletRequest req)
+    {
+        return req.getParameter("url");
+    }
+
     private String retrieveAndRender(String rssUrl, HttpServletRequest req)
     {
         try
             ctx.putAll(rssService.createContext(feed, rssUrl, req.getParameterMap()));
             return renderFeedInline(ctx);
         }
+        catch (FeedRetrievalException ex)
+        {
+            log.debug("Exception retrieving and parsing RSS feed: " + ex.getMessage(), ex);
+            return "Unable to retrieve and render RSS feed";
+        }
         catch (RuntimeException ex)
         {
-            log.warn("Exception retrieving and parsing RSS feed: " + ex.getMessage());
-            log.error("Actual exception", ex);
-            return "Unable to retrieve and render RSS feed: " + ex.getMessage();
+            log.warn("Unexpected exception retrieving and parsing RSS feed: " + ex.getMessage(), ex);
+            return "Unable to retrieve and render RSS feed";
         }
     }
 

File src/main/resources/atlassian-plugin.xml

         <vendor name="${project.organization.name}" url="http://atlassian.com"/>
         <permissions>
         </permissions>
+        <param name="documentation.url">https://bitbucket.org/mrdon/confluence-remote-html-macros/wiki/Home</param>
     </plugin-info>
 
     <remote-plugin-container key="container" display-url="http://confluence-remote-html-macros.herokuapp.com/confluence-remote-html-macros" />

File src/main/resources/views/rss.vm

                     <br />
                     ## Encode descriptions
                     #set ($feedDescriptionHtml = $feed.description)
-                    <span class="smalltext">($descriptionRenderer.render($feed.descriptionEx))</span>
+                    #set ($safeDescription = $descriptionRenderer.render($feed.descriptionEx))
+                    #if ($safeDescription != "")
+                      <span class="smalltext">($safeDescription)</span>
+                    #end
                 #end
             </th>
         </tr>