Commits

Alex Wei  committed ed4d7d9

JST-1380 Fixed wrong feed uri, handled Confluence NPE for missing thumbnail, properly phrased "attached files to" and fixed dependency issue

  • Participants
  • Parent commits d79bbe1
  • Branches streams-refactor, streams-refactor-fisheye

Comments (0)

Files changed (6)

File streams-confluence-plugin/pom.xml

     <dependencyManagement>
         <dependencies>
             <dependency>
-                <groupId>commons-collections</groupId>
-                <artifactId>commons-collections</artifactId>
-                <version>3.2</version>
-                <scope>provided</scope>
-            </dependency>
-            <dependency>
                 <groupId>commons-httpclient</groupId>
                 <artifactId>commons-httpclient</artifactId>
                 <version>3.0</version>
         </dependency>
 
         <dependency>
+            <groupId>commons-collections</groupId>
+            <artifactId>commons-collections</artifactId>
+            <version>3.2</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-core</artifactId>
             <version>2.5.2</version>
         <cargo.start.phase>pre-integration-test</cargo.start.phase>
 
         <cargo.wait>false</cargo.wait>
+        <jvmargs>-Xmx512m -XX:MaxPermSize=256m</jvmargs>
         <maven.test.it.skip>${maven.test.skip}</maven.test.it.skip>
         <maven.test.unit.skip>${maven.test.skip}</maven.test.unit.skip>
         <tomcat.installer.url>http://repository.atlassian.com/maven2/org/apache/tomcat/apache-tomcat/5.5.20/apache-tomcat-5.5.20-jdk14.zip</tomcat.installer.url>

File streams-confluence-plugin/src/main/java/com/atlassian/streams/confluence/ConfluenceStreamsActivityProvider.java

 package com.atlassian.streams.confluence;
 
+import static com.atlassian.confluence.search.service.ContentTypeEnum.*;
+import static com.atlassian.streams.util.Preconditions.*;
+import static org.apache.commons.collections.CollectionUtils.*;
+import static org.apache.commons.lang.StringUtils.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.collections.Transformer;
+import org.apache.commons.collections.functors.NotNullPredicate;
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Qualifier;
+
 import com.atlassian.bonnie.Searchable;
 import com.atlassian.confluence.core.ConfluenceEntityObject;
 import com.atlassian.confluence.search.service.ContentTypeEnum;
-import static com.atlassian.confluence.search.service.ContentTypeEnum.*;
-import com.atlassian.confluence.search.v2.*;
+import com.atlassian.confluence.search.v2.InvalidSearchException;
+import com.atlassian.confluence.search.v2.ResultFilter;
+import com.atlassian.confluence.search.v2.Search;
+import com.atlassian.confluence.search.v2.SearchFilter;
+import com.atlassian.confluence.search.v2.SearchManager;
+import com.atlassian.confluence.search.v2.SearchQuery;
+import com.atlassian.confluence.search.v2.SearchResults;
+import com.atlassian.confluence.search.v2.SearchSort;
 import com.atlassian.confluence.search.v2.filter.SubsetResultFilter;
 import com.atlassian.confluence.search.v2.searchfilter.SiteSearchPermissionsSearchFilter;
 import com.atlassian.confluence.search.v2.sort.ModifiedSort;
 import com.atlassian.sal.api.ApplicationProperties;
 import com.atlassian.streams.api.ActivityRequest;
 import com.atlassian.streams.api.StreamsActivityProvider;
-import static com.atlassian.streams.util.Preconditions.*;
 import com.sun.syndication.feed.synd.SyndEntry;
 import com.sun.syndication.feed.synd.SyndFeed;
 import com.sun.syndication.feed.synd.SyndFeedImpl;
-import static org.apache.commons.collections.CollectionUtils.*;
-import org.apache.commons.collections.Transformer;
-import org.apache.commons.collections.functors.NotNullPredicate;
-import org.apache.log4j.Logger;
-import org.springframework.beans.factory.annotation.Qualifier;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.*;
 
 public class ConfluenceStreamsActivityProvider implements StreamsActivityProvider
 {
     private final static ContentTypeEnum[] CONTENT_TYPES =
             new ContentTypeEnum[]{PAGE, BLOG, COMMENT, ATTACHMENT};
 
-    public ConfluenceStreamsActivityProvider(@Qualifier("spaceManager") SpaceManager spaceManager,
-                                             @Qualifier("searchManager") SearchManager searchManager,
-                                             RemoteChangeReportFactory remoteChangeReportFactory,
-                                             SyndEntryAssembler syndEntryAssembler,
-                                             ApplicationProperties applicationProperties)
+    public ConfluenceStreamsActivityProvider(@Qualifier("spaceManager") final SpaceManager spaceManager,
+                                             @Qualifier("searchManager") final SearchManager searchManager,
+                                             final RemoteChangeReportFactory remoteChangeReportFactory,
+                                             final SyndEntryAssembler syndEntryAssembler,
+                                             final ApplicationProperties applicationProperties)
     {
         this.spaceManager = checkNotNull(spaceManager, "Space Manager");
         this.searchManager = checkNotNull(searchManager, "Confluence Search Manager V2");
      * @param activityRequest The request
      * @return The ATOM feed
      */
-    public SyndFeed getActivityFeed(ActivityRequest activityRequest)
+    public SyndFeed getActivityFeed(final ActivityRequest activityRequest)
     {
-        List<Searchable> searchables = search(activityRequest);
-        List<RemoteChangeReport> changeReports = buildChangeReports(searchables);
+        final List<Searchable> searchables = search(activityRequest);
+        final List<RemoteChangeReport> changeReports = buildChangeReports(searchables);
 
         return buildFeed(activityRequest, changeReports);
     }
         return true;
     }
 
-    private List<RemoteChangeReport> buildChangeReports(List<Searchable> searchables)
+    private List<RemoteChangeReport> buildChangeReports(final List<Searchable> searchables)
     {
-        List<RemoteChangeReport> changeReports = new ArrayList<RemoteChangeReport>(searchables.size());
+        final List<RemoteChangeReport> changeReports = new ArrayList<RemoteChangeReport>(searchables.size());
         try {
-            for (Searchable searchable : searchables) {
+            for (final Searchable searchable : searchables) {
                 if (searchable instanceof ConfluenceEntityObject) {
                     remoteChangeReportFactory.addChangeReports((ConfluenceEntityObject)searchable, changeReports, null);
                 }
             }
-        } catch (Exception e) {
+        } catch (final Exception e) {
             throw new RuntimeException(e);
         }
         filter(changeReports, NotNullPredicate.INSTANCE);
     }
 
     @SuppressWarnings("unchecked")
-    private SyndFeed buildFeed(ActivityRequest activityRequest, List<RemoteChangeReport> changeReports)
+    private SyndFeed buildFeed(final ActivityRequest activityRequest, final List<RemoteChangeReport> changeReports)
     {
-        HttpServletRequest request = activityRequest.getRequest();
-        String baseUri = request.getRequestURL().append(activityRequest.getRequest().getQueryString()).toString();
+        final HttpServletRequest request = activityRequest.getRequest();
+        final String queryString = request.getQueryString();
+		final String baseUri = request.getRequestURL().append(isEmpty(queryString) ? "" : "?" + queryString).toString();
 
-        SyndFeedImpl feed = new SyndFeedImpl();
+        final SyndFeedImpl feed = new SyndFeedImpl();
         feed.setUri(baseUri);
         feed.setTitle("Activity Stream for " + getSpaceNames(activityRequest.getKeys()));
         feed.setDescription("Activity for Confluence.");
         // feed type will be set by the caller after return.
 
         // transform the search results to feed entries
-        List<SyndEntry> syndEntries = (List<SyndEntry>) collect(changeReports, new Transformer()
+        final List<SyndEntry> syndEntries = (List<SyndEntry>) collect(changeReports, new Transformer()
         {
-            public Object transform(Object input)
+            public Object transform(final Object input)
             {
                 return syndEntryAssembler.assemble(input);
             }
         return feed;
     }
 
-    private String getSpaceNames(List<String> spaceKeys)
+    private String getSpaceNames(final List<String> spaceKeys)
     {
         if (isEmpty(spaceKeys))
         {
             return "all spaces";
         }
         filter(spaceKeys, NotNullPredicate.INSTANCE);
-        StringBuilder result = new StringBuilder();
-        for (Iterator<String> i = spaceKeys.iterator(); i.hasNext();)
+        final StringBuilder result = new StringBuilder();
+        for (final Iterator<String> i = spaceKeys.iterator(); i.hasNext();)
         {
-            Space space = spaceManager.getSpace(i.next());
+            final Space space = spaceManager.getSpace(i.next());
             if (result.length() > 0)
             {
                 result.append(i.hasNext() ? "," : " and ");
         return result.toString();
     }
 
-    private List<Searchable> search(ActivityRequest request)
+    private List<Searchable> search(final ActivityRequest request)
     {
-        Search search = buildSearch(request);
+        final Search search = buildSearch(request);
 
         try
         {
-            SearchResults results = searchManager.search(search);
+            final SearchResults results = searchManager.search(search);
             if (results.size() < 1)
             {
                 return Collections.emptyList();
             }
             return searchManager.convertToEntities(results, true);
         }
-        catch (InvalidSearchException e)
+        catch (final InvalidSearchException e)
         {
             return Collections.emptyList();
         }
     }
 
-    private Search buildSearch(ActivityRequest request)
+    private Search buildSearch(final ActivityRequest request)
     {
-        SearchQuery query = buildQuery(request);
-        SearchSort sort = new ModifiedSort(SearchSort.Order.DESCENDING); // latest modified content first
-        SearchFilter securityFilter = SiteSearchPermissionsSearchFilter.getInstance();
-        ResultFilter resultFilter = new SubsetResultFilter(request.getMaxResults());
+        final SearchQuery query = buildQuery(request);
+        final SearchSort sort = new ModifiedSort(SearchSort.Order.DESCENDING); // latest modified content first
+        final SearchFilter securityFilter = SiteSearchPermissionsSearchFilter.getInstance();
+        final ResultFilter resultFilter = new SubsetResultFilter(request.getMaxResults());
 
         return new Search(query, sort, securityFilter, resultFilter);
     }
 
-    private SearchQuery buildQuery(ActivityRequest request)
+    private SearchQuery buildQuery(final ActivityRequest request)
     {
         return new ConfluenceSearchQueryBuilder()
                 .createdOrLastModifiedBy(request.getFilterUsers())

File streams-confluence-plugin/src/main/java/com/atlassian/streams/confluence/RemoteAttachmentChangeReport.java

 package com.atlassian.streams.confluence;
 
-import com.atlassian.confluence.pages.Attachment;
-import org.apache.commons.lang.ObjectUtils;
-import org.apache.commons.lang.time.DateUtils;
+import static org.apache.commons.lang.StringUtils.*;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.commons.lang.time.DateUtils;
+
+import com.atlassian.confluence.pages.Attachment;
+
 public class RemoteAttachmentChangeReport extends RemoteChangeReport
 {
     private List<RemoteAttachment> attachments = new ArrayList<RemoteAttachment>();
         super();
     }
 
-    public RemoteAttachmentChangeReport(Attachment attachment)
+    public RemoteAttachmentChangeReport(final Attachment attachment)
     {
         setChangedBy(attachment.getLastModifierName());
         setModified(attachment.getLastModificationDate());
         setType("attachment." + (attachment.isNew() ? "added" : "modified"));
-        setSummary(attachment.getComment());
+        setSummary(trimToEmpty(attachment.getComment()));
 
         downloadUrl = attachment.getContent().getAttachmentsUrlPath();
 
         setUrlPath(extractUrlPath(attachment));
     }
 
-    protected static String extractUrlPath(Attachment attachment)
+    protected static String extractUrlPath(final Attachment attachment)
     {
         return attachment.getContent().getUrlPath();
     }
 
-    public void addRemoteAttachment(RemoteAttachment attachment)
+    public void addRemoteAttachment(final RemoteAttachment attachment)
     {
         attachments.add(attachment);
     }
 
     public RemoteAttachment[] getAttachments()
     {
-        RemoteAttachment[] array = new RemoteAttachment[attachments.size()];
+        final RemoteAttachment[] array = new RemoteAttachment[attachments.size()];
         for (int i = 0; i < attachments.size(); i++)
             array[i] = attachments.get(i);
         return array;
     }
 
-    public void setAttachments(RemoteAttachment[] attachments)
+    public void setAttachments(final RemoteAttachment[] attachments)
     {
         this.attachments = Arrays.asList(attachments);
     }
         return downloadUrl;
     }
 
-    public void setDownloadUrl(String downloadUrl)
+    public void setDownloadUrl(final String downloadUrl)
     {
         this.downloadUrl = downloadUrl;
     }
 
-    public static RemoteAttachmentChangeReport getMatchingChange(List changeReports, Attachment attachment)
+    public static RemoteAttachmentChangeReport getMatchingChange(final List<? extends RemoteChangeReport> changeReports, final Attachment attachment)
     {
-        for (Object changeReport : changeReports)
+        for (final RemoteChangeReport remoteChangeReport : changeReports)
         {
-            RemoteChangeReport remoteChangeReport = (RemoteChangeReport) changeReport;
             if (remoteChangeReport instanceof RemoteAttachmentChangeReport)
             {
-                RemoteAttachmentChangeReport attachmentReport = (RemoteAttachmentChangeReport) remoteChangeReport;
+                final RemoteAttachmentChangeReport attachmentReport = (RemoteAttachmentChangeReport) remoteChangeReport;
 
                 if (ObjectUtils.equals(attachmentReport.getChangedBy(), attachment.getLastModifierName())
                     && attachmentReport.getUrlPath().equals(extractUrlPath(attachment))

File streams-confluence-plugin/src/main/java/com/atlassian/streams/confluence/RemoteChangeReportFactory.java

 package com.atlassian.streams.confluence;
 
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.util.List;
+
+import org.apache.commons.jrcs.diff.DifferentiationFailedException;
+import org.apache.log4j.Logger;
+
 import com.atlassian.confluence.core.ConfluenceEntityObject;
 import com.atlassian.confluence.core.ContentEntityObject;
 import com.atlassian.confluence.pages.Attachment;
 import com.atlassian.confluence.util.GeneralUtil;
 import com.atlassian.core.util.thumbnail.Thumbnail;
 import com.atlassian.renderer.WikiStyleRenderer;
-import org.apache.commons.jrcs.diff.DifferentiationFailedException;
-import org.apache.log4j.Logger;
-
-import java.io.FileNotFoundException;
-import java.net.MalformedURLException;
-import java.util.List;
 
 public class RemoteChangeReportFactory
 {
 
 	private WikiStyleRenderer wikiStyleRenderer;
 
-    public void setPageManager(PageManager pageManager)
+    public void setPageManager(final PageManager pageManager)
     {
         this.pageManager = pageManager;
     }
 
-    public void setThumbnailManager(ThumbnailManager thumbnailManager)
+    public void setThumbnailManager(final ThumbnailManager thumbnailManager)
     {
         this.thumbnailManager = thumbnailManager;
     }
     
-    public void setWikiStyleRenderer(WikiStyleRenderer wikiStyleRenderer) 
+    public void setWikiStyleRenderer(final WikiStyleRenderer wikiStyleRenderer) 
     {
         this.wikiStyleRenderer = wikiStyleRenderer;
     }
 
-    public void setUserAccessor(UserAccessor userAccessor)
+    public void setUserAccessor(final UserAccessor userAccessor)
     {
     }
 
-    public void addChangeReports(ConfluenceEntityObject entity, List<RemoteChangeReport> currentChangeReports, String filterUser)
+    public void addChangeReports(final ConfluenceEntityObject entity, final List<RemoteChangeReport> currentChangeReports, final String filterUser)
         throws Exception
     {
         if (entity instanceof Attachment)
      * @param currentChangeReports
      * @return
      */
-    private void addReports(Attachment attachment, List<RemoteChangeReport> currentChangeReports, String filterUser)
+    private void addReports(final Attachment attachment, final List<RemoteChangeReport> currentChangeReports, final String filterUser)
     {
         // see if there's already an attachment change report that we can attach this change to
         // (i.e. different attachment, same person, time, etc)
                 currentChangeReports.add(report);
         }
 
-        RemoteAttachment remoteAttachment = new RemoteAttachment(attachment);
+        final RemoteAttachment remoteAttachment = new RemoteAttachment(attachment);
 
         if (thumbnailManager.isThumbnailable(attachment))
         {
             try
             {
-                Thumbnail thumbnail = thumbnailManager.getThumbnail(attachment);
+            	// TODO: getThumbnail can throw IllegalArgumentException if the file is not found (requires re-indexing)
+                final Thumbnail thumbnail = thumbnailManager.getThumbnail(attachment);
                 //todo: we actually need to use the url to thumbnail
                 remoteAttachment.setPreview(attachment.getDownloadPath(),
                                             thumbnail.getHeight(), thumbnail.getWidth());
             }
-            catch (MalformedURLException e)
+            catch (final MalformedURLException e)
             {
                 log.warn("MalformedURLException caught trying to get a thumbnail.");
                 log.debug("The exception was: ", e);
             }
-            catch (FileNotFoundException e)
+            catch (final FileNotFoundException e)
             {
                 log.warn("FileNotFoundException caught trying to get a thumbnail.");
                 log.debug("The exception was: ", e);
             }
-
+            // Confluence throws an IllegalArgumentException if the thumbnail file cannot be opened
+            // This is probably because re-indexing is required as it seems to solve the problem.
+            catch (final Exception e)
+            {
+            	log.warn("Exception caught trying to get a thumbnail.", e);
+            }
         }
 
         report.addRemoteAttachment(remoteAttachment);
      * @param page
      * @return
      */
-    private void addReports(Page page, List<RemoteChangeReport> currentChangeReports, String filterUser) throws Exception
+    private void addReports(final Page page, final List<RemoteChangeReport> currentChangeReports, final String filterUser) throws Exception
     {
-        RemoteChangeReport report = new RemoteChangeReport(page, wikiStyleRenderer);
+        final RemoteChangeReport report = new RemoteChangeReport(page, wikiStyleRenderer);
 
-        ContentEntityObject lastVersion = pageManager.getPreviousVersion(page);
+        final ContentEntityObject lastVersion = pageManager.getPreviousVersion(page);
 
         if (page.isVersionCommentAvailable())
         {
         {
             try
             {
-                DiffResult result = DiffUtils.generateDiff(lastVersion, page);
+                final DiffResult result = DiffUtils.generateDiff(lastVersion, page);
                 report.setSummary(
                     GeneralUtil.getI18n().getText("stream.item.confluence.action.page.modified.diff.with.number",
                                                   new Object[]{getPageDiffUrl(page), result.getNumChanges()}));
             }
-            catch (DifferentiationFailedException e)
+            catch (final DifferentiationFailedException e)
             {
                 log.warn("Problem getting a diff for page " + page.getTitle() + ".", e);
                 report.setSummary(GeneralUtil.getI18n().getText("stream.item.confluence.action.page.modified.diff.no.number",
     /**
      * overrides default type setting behavior so that there's no difference noted between adding and editing comments
      */
-    private void addReports(Comment comment, List<RemoteChangeReport> currentChangeReports, String filterUser)
+    private void addReports(final Comment comment, final List<RemoteChangeReport> currentChangeReports, final String filterUser)
     {
-        RemoteCommentChangeReport report = new RemoteCommentChangeReport(comment, wikiStyleRenderer);
+        final RemoteCommentChangeReport report = new RemoteCommentChangeReport(comment, wikiStyleRenderer);
         report.setType("comment.added");
         if (report.matchesFilterUser(filterUser))
             currentChangeReports.add(report);
     }
 
-    private void addReports(ContentEntityObject entity, List<RemoteChangeReport> currentChangeReports, String filterUser)
+    private void addReports(final ContentEntityObject entity, final List<RemoteChangeReport> currentChangeReports, final String filterUser)
     {
-        RemoteChangeReport report = new RemoteChangeReport(entity, wikiStyleRenderer);
+        final RemoteChangeReport report = new RemoteChangeReport(entity, wikiStyleRenderer);
         if (report.matchesFilterUser(filterUser))
             currentChangeReports.add(report);
     }
 
-    private String getPageDiffUrl(Page page)
+    private String getPageDiffUrl(final Page page)
     {
-        StringBuilder diffUrl = new StringBuilder();
+        final StringBuilder diffUrl = new StringBuilder();
         diffUrl.append(GeneralUtil.getGlobalSettings().getBaseUrl());
         diffUrl.append("/pages/viewpreviousversions.action?pageId=" + page.getId());
         return diffUrl.toString();

File streams-confluence-plugin/src/main/java/com/atlassian/streams/confluence/assembler/RemoteAttachmentChangeReportSyndEntryAssembler.java

 package com.atlassian.streams.confluence.assembler;
 
+import java.awt.Dimension;
+
 import com.atlassian.sal.api.ApplicationProperties;
 import com.atlassian.sal.api.message.I18nResolver;
 import com.atlassian.streams.builder.StreamsBuilderFactory;
 import com.atlassian.streams.confluence.RemoteAttachmentChangeReport;
 import com.atlassian.streams.confluence.RemoteChangeReport;
 
-import java.awt.*;
-
 /**
  * Created by IntelliJ IDEA. To change body of created methods use File | Settings | File Templates.
  *
 
     private static final int MAX_THUMBNAIL_HEIGHT = 100;
 
-    public RemoteAttachmentChangeReportSyndEntryAssembler(StreamsBuilderFactory streamsBuilderFactory,
-                                                          ApplicationProperties applicationProperties,
-                                                          I18nResolver i18nResolver)
+    public RemoteAttachmentChangeReportSyndEntryAssembler(final StreamsBuilderFactory streamsBuilderFactory,
+                                                          final ApplicationProperties applicationProperties,
+                                                          final I18nResolver i18nResolver)
     {
         super(streamsBuilderFactory, applicationProperties, i18nResolver);
     }
 
     @Override
-    public boolean canHandle(Object source)
+    public boolean canHandle(final Object source)
     {
         return source instanceof RemoteAttachmentChangeReport;
     }
 
     @Override
-    protected String buildActionObjectHtml(RemoteChangeReport source)
+    protected String buildActionObjectHtml(final RemoteChangeReport source)
     {
         if (!(source instanceof RemoteAttachmentChangeReport)) return super.buildActionObjectHtml(source);
-        RemoteAttachmentChangeReport changeReport = (RemoteAttachmentChangeReport)source;
-        String action = changeReport.getType();
-        String fileUrl = prependBaseUrl(changeReport.getDownloadUrl());
+        final RemoteAttachmentChangeReport changeReport = (RemoteAttachmentChangeReport)source;
+        final String action = changeReport.getType();
+        final String fileUrl = prependBaseUrl(changeReport.getDownloadUrl());
         return action.startsWith(ATTACHMENT) ?
                 getText(ATTACHED_ACTION_BUNDLE_KEY,
-                        nullSafeLink(fileUrl, nullSafeHtmlEncode(getText(ATTACHED_FILES_BUNDLE_KEY)))) :
+                        nullSafeLink(fileUrl, nullSafeHtmlEncode(getText(ATTACHED_FILES_BUNDLE_KEY))),
+                        nullSafeLink(source.getUrlPath(), source.getTitle())) :
                 super.buildActionObjectHtml(changeReport);
     }
 
     @Override
-    protected String buildDescriptionHtml(RemoteChangeReport source)
+    protected String buildDescriptionHtml(final RemoteChangeReport source)
     {
         if (!(source instanceof RemoteAttachmentChangeReport)) return super.buildDescriptionHtml(source);
-        RemoteAttachmentChangeReport changeReport = (RemoteAttachmentChangeReport)source;
-        StringBuilder html = new StringBuilder();
+        final RemoteAttachmentChangeReport changeReport = (RemoteAttachmentChangeReport)source;
+        final StringBuilder html = new StringBuilder();
         if (changeReport.getAttachments() != null && changeReport.getAttachments().length > 0)
         {
             html.append("<div class=\"thumbnails\">");
-            for (RemoteAttachment a : changeReport.getAttachments())
+            for (final RemoteAttachment a : changeReport.getAttachments())
             {
                 if (a.isHasPreview())
                 {
-                    Dimension d = scaleToThumbnailSize(a.getWidth(), a.getHeight());
+                    final Dimension d = scaleToThumbnailSize(a.getWidth(), a.getHeight());
                     html.append("<a href=\"").append(prependBaseUrl(a.getDownloadUrl())).append("\" >");
                     html.append("<img src=\"").append(prependBaseUrl(a.getPreviewUrl())).append("\" ");
                     html.append("width=\"").append(d.width).append("\" ");
         return html.toString();
     }
 
-    public static Dimension scaleToThumbnailSize(int width, int height)
+    public static Dimension scaleToThumbnailSize(final int width, final int height)
     {
         if (height == 0)
             throw new IllegalArgumentException("height cannot be zero.");
 
-        double scaleFactor = height > MAX_THUMBNAIL_HEIGHT ? ((double) MAX_THUMBNAIL_HEIGHT) / (double) height : 1;
+        final double scaleFactor = height > MAX_THUMBNAIL_HEIGHT ? ((double) MAX_THUMBNAIL_HEIGHT) / (double) height : 1;
         return new Dimension((int) (width * scaleFactor), (int) (height * scaleFactor));
     }
 }

File streams-confluence-plugin/src/main/resources/com/atlassian/streams/confluence/i18n.properties

 stream.item.title.people.multiple={0} and {1} others
 
 # args: 0=attachment info (link/label)
-stream.item.action.attached=attached {0} to
+stream.item.action.attached=attached {0} to {1}
 stream.item.action.attached.files=files
 
 
 
 # Activity Linking Text
 # args: 0=number of comments
-stream.item.confluence.linkingtext={0,choice,0#.|0< saying}
+stream.item.confluence.linkingtext={0,choice,0#|0< saying}
 
 # args: 0=title
 stream.item.confluence.diff=making