/******************************************************************************* * Copyright (c) 2010-2014 SAP AG and others. * 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: * SAP AG - initial API and implementation *******************************************************************************/ package org.eclipse.skalli.services.feed; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.eclipse.skalli.commons.CollectionUtils; import org.eclipse.skalli.commons.HttpUtils; import org.eclipse.skalli.services.destination.DestinationService; import org.eclipse.skalli.services.destination.Destinations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.SyndLink; import com.sun.syndication.feed.synd.SyndPerson; import com.sun.syndication.io.FeedException; import com.sun.syndication.io.SyndFeedInput; /** * Generic feed updater for a given, usually project specific URL. * This feed updater is based on {@link com.sun.syndication.feed.synd.SyndFeed}. * It handles all RSS versions and Atom 0.3. * <p> * Note, this feed updater tries to retrieve a suitable HTTP client from * one of the registered {@link DestinationService destination services}. * It will fail if no suitable destination for the RSS feed is available. */ public class URLFeedUpdater implements FeedUpdater { private static final Logger LOG = LoggerFactory.getLogger(URLFeedUpdater.class); private URL url; private String source; private String caption; private UUID projectId; /** * Returns a feed updater for the given URL. * * @param url the URL of the feed. * @param source the source identifier of the feed to assign to feed entries. * @param caption the human readable caption of the feed. If no caption is specified, * the value of <code>source</code> is used. * @param projectId the unique identifier of the project to assign to feed entries, or * <code>null</code> if the feed is not related to an individual project. */ public URLFeedUpdater(URL url, String source, String caption, UUID projectId) { if (url == null) { throw new IllegalArgumentException("parameter 'url' must not be null"); } if (StringUtils.isBlank(source)) { throw new IllegalArgumentException("parameter 'source' must not be null or blank"); } this.url = url; this.source = source; this.caption = StringUtils.isNotBlank(caption)? caption : source; this.projectId = projectId; } /** * Reads entries from the feed assuming that a matching destination is available * from one of the registered {@link DestinationService destination services}. */ @Override public List<FeedEntry> updateFeed(FeedFactory feedFactory) { List<FeedEntry> entries = new ArrayList<FeedEntry>(); try { SyndFeed syndFeed = getSyndFeed(); for (Object o : syndFeed.getEntries()) { entries.add(toFeedEntry(feedFactory, (SyndEntry)o)); } } catch (Exception e) { LOG.error(MessageFormat.format("Failed to update feed {0}:\n{1}", url.toString(), e.getMessage()), e); } return entries; } @Override public String getSource() { return source; } @Override public String getCaption() { return caption; } /** * Returns the unique identifier of the project associated with this feed updater. * * @return the unique identifier of a project, * or <code>null</code> if the feed is not associated with an individual project. */ public UUID getProjectId() { return projectId; } /** * Returns the remote feed. This implementation retrieves a suitable * {@link HttpClient} from {@link Destinations} and issues a GET request * to the feed's URL. * * @return a {@link SyndFeed} instance from which the feed content can * be read, never <code>null</code>. * * @throws IOException if an i/o error occured. * @throws FeedException if the feed updater failed to read data from the remote feed, e.g. because * no suitable HTTP client could be created or the remote feed reported an error. */ protected SyndFeed getSyndFeed() throws IOException, FeedException { HttpClient client = Destinations.getClient(url); if (client == null) { throw new FeedException(MessageFormat.format("Failed to create HTTP connection to feed {0}", url)); } Reader reader = null; HttpResponse response = null; try { LOG.info("GET " + url); //$NON-NLS-1$ HttpGet method = new HttpGet(url.toExternalForm()); response = client.execute(method); int status = response.getStatusLine().getStatusCode(); LOG.info(status + " " + response.getStatusLine().getReasonPhrase()); //$NON-NLS-1$ if (status != HttpStatus.SC_OK) { throw new FeedException(MessageFormat.format("Failed to retrieve feed {0}", url)); } reader = new InputStreamReader(response.getEntity().getContent(), "UTF-8"); //$NON-NLS-1$ return new SyndFeedInput().build(reader); } finally { IOUtils.closeQuietly(reader); HttpUtils.consumeQuietly(response); } } /** * Converts a {@link SyndEntry} to a corresponding {@link FeedEntry}. * * @param factory the feed factory to use for creating feed entries. * @param syndEntry the {@link SyndEntry} to convert. * * @return a new {@link FeedEntry} instance, or <code>null</code> if * <code>syndEntry</code> was <code>null</code>. */ protected FeedEntry toFeedEntry(FeedFactory factory, SyndEntry syndEntry) { if (syndEntry == null) { return null; } FeedEntry entry = factory.createEntry(); entry.setSource(source); entry.setProjectId(projectId); entry.setTitle(syndEntry.getTitle()); if (CollectionUtils.isNotBlank(syndEntry.getLinks())) { SyndLink syndLink = (SyndLink) syndEntry.getLinks().get(0); entry.getLink().setHref(syndLink.getHref()); entry.getLink().setTitle(syndLink.getTitle()); } if (CollectionUtils.isNotBlank(syndEntry.getContents())) { SyndContent syndContent = (SyndContent) syndEntry.getContents().get(0); entry.getContent().setType(syndContent.getType()); entry.getContent().setValue(syndContent.getValue()); } if (CollectionUtils.isNotBlank(syndEntry.getAuthors())) { SyndPerson syndAuthor = (SyndPerson) syndEntry.getAuthors().get(0); entry.getAuthor().setName(syndAuthor.getName()); entry.getAuthor().setEmail(syndAuthor.getEmail()); } Date publishedDate = syndEntry.getPublishedDate(); if (publishedDate == null) { publishedDate = syndEntry.getUpdatedDate(); if (publishedDate == null) { publishedDate = new Date(); } } entry.setPublished(publishedDate); return entry; } }