/*******************************************************************************
* Copyright (c) 2012 - 2013 Pivotal Software, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.dashboard.internal.ui.editors;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.mylyn.internal.discovery.ui.DiscoveryUi;
import org.eclipse.ui.progress.IProgressConstants;
import org.osgi.framework.Version;
import org.springsource.ide.eclipse.commons.core.HttpUtil;
import org.springsource.ide.eclipse.commons.core.StatusHandler;
import org.springsource.ide.eclipse.commons.ui.StsUiImages;
import org.springsource.ide.eclipse.dashboard.internal.ui.IdeUiPlugin;
import org.springsource.ide.eclipse.dashboard.internal.ui.util.IdeUiUtils;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;
/**
* @author Leo Dos Santos
* @author Steffen Pingel
* @author Terry Denney
* @author Christian Dupuis
* @author Miles Parker
*/
public class AggregateFeedJob extends Job {
public static final Object CONTENT_FAMILY = new Object();
private final Map<String, String> feedsToIconsMap;
private final CachedFeedsManager feedManager;
private final FeedsReader feedReader;
private String feedName;
private List<UpdateNotification> notifications = new ArrayList<UpdateNotification>();
public AggregateFeedJob(Map<String, String> feedsToIconsMap, String feedName) {
this("Downloading RSS feeds", feedsToIconsMap, feedName);
}
public AggregateFeedJob(String name, Map<String, String> feedsToIconsMap, String feedName) {
super(name);
this.feedsToIconsMap = feedsToIconsMap;
this.feedName = feedName;
feedReader = new FeedsReader();
feedManager = new CachedFeedsManager(feedName, feedsToIconsMap, feedReader);
setProperty(IProgressConstants.ICON_PROPERTY, StsUiImages.RSS);
}
@Override
public boolean belongsTo(Object family) {
return CONTENT_FAMILY == family;
}
public FeedsReader getFeedReader() {
return feedReader;
}
public boolean isCoveredBy(AggregateFeedJob other) {
if (other.feedsToIconsMap != null && this.feedsToIconsMap != null) {
return other.feedsToIconsMap.equals(this.feedsToIconsMap);
}
return false;
}
@Override
protected IStatus run(final IProgressMonitor monitor) {
synchronized (getClass()) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
Job[] buildJobs = Job.getJobManager().find(CONTENT_FAMILY);
for (Job curr : buildJobs) {
if (curr != this && curr instanceof AggregateFeedJob) {
AggregateFeedJob job = (AggregateFeedJob) curr;
if (job.isCoveredBy(this)) {
curr.cancel();
}
}
}
}
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
final CountDownLatch resultLatch = new CountDownLatch(1);
Runnable downloadRunnable = new Runnable() {
public void run() {
SyndFeedInput input = new SyndFeedInput();
Map<String, String> feedToContent = new HashMap<String, String>();
try {
Set<Entry<String, String>> entrySet = feedsToIconsMap.entrySet();
SubMonitor progress = SubMonitor.convert(monitor, entrySet.size());
if (!entrySet.isEmpty()) {
Iterator<Entry<String, String>> iter = entrySet.iterator();
while (iter.hasNext()) {
Entry<String, String> entry = iter.next();
String feedUrlStr = entry.getKey();
String iconPath = entry.getValue();
XmlReader reader;
if (feedUrlStr.startsWith("http")) {
reader = new XmlReader(HttpUtil.stream(new URI(feedUrlStr), progress.newChild(1)));
}
else {
InputStream stream = FileLocator.openStream(IdeUiPlugin.getDefault().getBundle(),
new Path(feedUrlStr), false);
reader = new XmlReader(stream);
}
StringBuilder cachedFeed = new StringBuilder();
char[] buffer = new char[256];
int length = 0;
while ((length = reader.read(buffer)) > 0) {
cachedFeed.append(buffer, 0, length);
}
reader.close();
feedReader.readFeeds(new StringReader(cachedFeed.toString()), input, iconPath);
feedToContent.put(feedUrlStr, cachedFeed.toString());
}
}
feedManager.cacheFeeds(feedToContent);
}
catch (Exception e) {
// do nothing, feeds read from cache
}
resultLatch.countDown();
}
};
try {
new Thread(downloadRunnable).start();
if (resultLatch.await(30, TimeUnit.SECONDS)) {
updateNotifications(monitor);
return Status.OK_STATUS;
}
else {
// if this code is reached, feeds were not read properly. Try
// reading from cache if it exists
try {
feedManager.readCachedFeeds(monitor);
}
catch (Exception e) {
StatusHandler.log(new Status(IStatus.ERROR, IdeUiPlugin.PLUGIN_ID,
"An unexpected error occurred while retrieving feed content from cache.", e));
}
}
}
catch (InterruptedException e) {
}
updateNotifications(monitor);
return Status.CANCEL_STATUS;
}
private void updateNotifications(IProgressMonitor monitor) {
Map<SyndEntry, SyndFeed> entryToFeed = getFeedReader().getFeedsWithEntries();
Set<SyndEntry> entries = entryToFeed.keySet();
Set<String> installedFeatures = null;
try {
installedFeatures = DiscoveryUi.createInstallJob().getInstalledFeatures(monitor);
}
catch (NullPointerException e) {
// profile is not available
}
Dictionary<Object, Object> environment = new Hashtable<Object, Object>(System.getProperties());
// make sure the entries are sorted correctly
List<SyndEntry> sortedEntries = new ArrayList<SyndEntry>(entries);
Collections.sort(sortedEntries, new Comparator<SyndEntry>() {
public int compare(SyndEntry o1, SyndEntry o2) {
Date o1Date = o1.getPublishedDate() != null ? o1.getPublishedDate() : o1.getUpdatedDate();
Date o2Date = o2.getPublishedDate() != null ? o2.getPublishedDate() : o2.getUpdatedDate();
if (o1Date == null && o2Date == null) {
return 0;
} else if (o1Date == null) {
return -1;
} else if (o2Date == null) {
return 1;
} else {
return o2Date.compareTo(o1Date);
}
}
});
Version ideVersion = IdeUiUtils.getVersion();
Set<UpdateNotification> notificationsSet = new HashSet<UpdateNotification>(notifications);
for (SyndEntry entry : sortedEntries) {
UpdateNotification notification = new UpdateNotification(entry);
if (notification.matches(ideVersion, installedFeatures, environment)) {
notificationsSet.add(notification);
}
}
notifications = new ArrayList<UpdateNotification>(notificationsSet);
}
public List<UpdateNotification> getNotifications() {
return notifications;
}
public String getFeedName() {
return feedName;
}
}