Skip to content

Commit

Permalink
Merge pull request #13 from futures/RSSFeed
Browse files Browse the repository at this point in the history
RSS feed is available as a JAX-RS resource
  • Loading branch information
cbeer committed Feb 13, 2013
2 parents 51755b0 + ab5f2bd commit 5f4ab72
Show file tree
Hide file tree
Showing 10 changed files with 404 additions and 0 deletions.
73 changes: 73 additions & 0 deletions fcrepo-rss/pom.xml
@@ -0,0 +1,73 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo</artifactId>
<version>4.0-SNAPSHOT</version>
</parent>
<artifactId>fcrepo-rss</artifactId>
<name>Fedora RSS Feed</name>
<description>Provides a simple RSS feed of repository events.</description>


<dependencies>
<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-kernel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-http-commons</artifactId>
<version>${project.version}</version>
</dependency>

<!-- test gear -->
<dependency>
<groupId>org.fcrepo</groupId>
<artifactId>fcrepo-legacy-api</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxrs</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-extension-providers</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>

<!-- This dependency is for compile-time: it keeps this module independent
of any given choice of JAX-RS implementation. It must be _after_ the test
gear. Otherwise it will get loaded during test phase, but because this is
just an API, the tests will not be able to execute. -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.rome</groupId>
<artifactId>rome</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>

</project>
109 changes: 109 additions & 0 deletions fcrepo-rss/src/main/java/org/fcrepo/syndication/RSSPublisher.java
@@ -0,0 +1,109 @@

package org.fcrepo.syndication;

import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.Lists.transform;
import static org.fcrepo.utils.EventType.getEventType;

import java.io.ByteArrayInputStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.jcr.RepositoryException;
import javax.jcr.observation.Event;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.xml.transform.stream.StreamSource;

import org.fcrepo.AbstractResource;
import org.joda.time.DateTime;
import org.modeshape.common.SystemFailureException;

import com.google.common.base.Function;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;

@Path("/rss")
public class RSSPublisher extends AbstractResource {

private static final Integer FEED_LENGTH = 10;

private static final String FEED_TYPE = "rss_2.0";

private static final String FEED_TITLE = "What's happening in Fedora";

private static final String FEED_DESCRIPTION = FEED_TITLE;

@Inject
EventBus eventBus;

private BlockingQueue<Event> feedQueue = new ArrayBlockingQueue<Event>(
FEED_LENGTH);

private SyndFeed feed = new SyndFeedImpl();

@GET
@Path("")
@Produces("application/rss+xml")
public StreamSource getFeed() throws FeedException {
feed.setLink(uriInfo.getBaseUri().toString());
feed.setEntries(transform(copyOf(feedQueue), event2entry));
// TODO ought to make this stream, not go through a string
return new StreamSource(new ByteArrayInputStream(new SyndFeedOutput()
.outputString(feed).getBytes()));
}

private Function<Event, SyndEntry> event2entry =
new Function<Event, SyndEntry>() {

@Override
public SyndEntry apply(Event event) {
SyndEntry entry = new SyndEntryImpl();
try {
entry.setTitle(event.getIdentifier());
entry.setLink(event.getPath());
entry.setPublishedDate(new DateTime(event.getDate())
.toDate());
SyndContent description = new SyndContentImpl();
description.setType("text/plain");
description.setValue(getEventType(event.getType())
.toString());
entry.setDescription(description);
} catch (RepositoryException e) {
throw new SystemFailureException(e);
}
return entry;
}

};

@PostConstruct
public void initialize() {
eventBus.register(this);
feed.setFeedType(FEED_TYPE);
feed.setTitle(FEED_TITLE);
feed.setDescription(FEED_DESCRIPTION);
}

@Subscribe
public void newEvent(Event event) {
if (feedQueue.remainingCapacity() > 0) {
feedQueue.offer(event);
} else {
feedQueue.poll();
feedQueue.offer(event);
}
}

}
@@ -0,0 +1,58 @@

package org.fcrepo.syndication;

import static java.lang.Integer.parseInt;
import static java.lang.System.getProperty;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
public abstract class AbstractResourceTest {

protected Logger logger;

@Before
public void setLogger() {
logger = LoggerFactory.getLogger(this.getClass());
}

protected static final int SERVER_PORT = parseInt(getProperty("test.port",
"8080"));

protected static final String HOSTNAME = "localhost";

protected static final String serverAddress = "http://" + HOSTNAME + ":" +
SERVER_PORT;

protected final PoolingClientConnectionManager connectionManager =
new PoolingClientConnectionManager();

protected static HttpClient client;

public AbstractResourceTest() {
connectionManager.setMaxTotal(Integer.MAX_VALUE);
connectionManager.setDefaultMaxPerRoute(5);
connectionManager.closeIdleConnections(3, TimeUnit.SECONDS);
client = new DefaultHttpClient(connectionManager);
}

protected int getStatus(HttpUriRequest method)
throws ClientProtocolException, IOException {
logger.debug("Executing: " + method.getMethod() + " to " +
method.getURI());
return client.execute(method).getStatusLine().getStatusCode();
}

}
38 changes: 38 additions & 0 deletions fcrepo-rss/src/test/java/org/fcrepo/syndication/RSSTest.java
@@ -0,0 +1,38 @@

package org.fcrepo.syndication;

import static java.util.regex.Pattern.DOTALL;
import static java.util.regex.Pattern.compile;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;

@ContextConfiguration({"/spring-test/repo.xml", "/spring-test/rest.xml",
"/spring-test/eventing.xml"})
public class RSSTest extends AbstractResourceTest {

final private Logger logger = LoggerFactory.getLogger(RSSTest.class);

@Test
public void testRSS() throws Exception {

assertEquals(201, getStatus(new HttpPost(serverAddress +
"/objects/RSSTESTPID")));

HttpGet getRSSMethod = new HttpGet(serverAddress + "/rss");
HttpResponse response = client.execute(getRSSMethod);
assertEquals(200, response.getStatusLine().getStatusCode());
String content = EntityUtils.toString(response.getEntity());
logger.debug("Retrieved RSS feed:\n" + content);
assertTrue("Didn't find the test PID in RSS!", compile("RSSTESTPID",
DOTALL).matcher(content).find());
}
}
25 changes: 25 additions & 0 deletions fcrepo-rss/src/test/resources/logback-test.xml
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%p %d{HH:mm:ss.SSS} \(%c{0}\) %m%n</pattern>
</encoder>
</appender>

<logger name="org.fcrepo" additivity="false" level="DEBUG">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.modeshape" additivity="false" level="WARN">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.apache.cxf" additivity="false" level="WARN">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.apache.http.client" additivity="false" level="DEBUG">
<appender-ref ref="STDOUT"/>
</logger>
<root additivity="false" level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
24 changes: 24 additions & 0 deletions fcrepo-rss/src/test/resources/spring-test/eventing.xml
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<!-- Context that suports event management, including the internal
event bus (fedoraInternalEventBus) -->

<context:annotation-config/>

<!-- listener that moves JCR Events to the Fedora internal event bus -->
<bean class="org.fcrepo.observer.SimpleObserver"/>

<!-- used by bean above to filter which events get put on the bus -->
<bean name="fedoraEventFilter" class="org.fcrepo.observer.DefaultFilter"/>

<!-- Fedora's lightweight internal event bus. Currently memory-resident.-->
<bean name="fedoraInternalEventBus" class="com.google.common.eventbus.EventBus"/>


</beans>
24 changes: 24 additions & 0 deletions fcrepo-rss/src/test/resources/spring-test/repo.xml
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<!-- Context that supports the actual ModeShape JCR itself -->

<context:annotation-config/>

<bean name="modeshapeRepofactory"
class="org.fcrepo.spring.ModeShapeRepositoryFactoryBean"/>

<bean class="org.modeshape.jcr.JcrRepositoryFactory"/>

<bean class="org.springframework.core.io.ClassPathResource">
<qualifier type="ModeShapeRepositoryConfiguration"/>
<constructor-arg type="String" value="test_repository.json"/>
</bean>


</beans>
33 changes: 33 additions & 0 deletions fcrepo-rss/src/test/resources/spring-test/rest.xml
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml"/>

<context:property-placeholder/>

<!-- Context that houses JAX-RS Resources that compose the API
as well as some utility gear. -->

<context:annotation-config/>

<jaxrs:server address="http://localhost:${test.port:8080}">
<jaxrs:serviceBeans>
<bean class="org.fcrepo.api.legacy.FedoraObjects"/>
<bean class="org.fcrepo.syndication.RSSPublisher"/>
</jaxrs:serviceBeans>
</jaxrs:server>

<!-- Mints PIDs-->
<bean class="org.fcrepo.identifiers.UUIDPidMinter"/>


</beans>

0 comments on commit 5f4ab72

Please sign in to comment.