diff --git a/org.springframework.web/ivy.xml b/org.springframework.web/ivy.xml index eba5db88746..a779d41d738 100644 --- a/org.springframework.web/ivy.xml +++ b/org.springframework.web/ivy.xml @@ -17,6 +17,7 @@ + @@ -34,6 +35,8 @@ + @@ -59,6 +62,8 @@ conf="optional, log4j->compile"/> + ROME package. + * + * @author Arjen Poutsma + * @since 3.0.2 + * @see AtomFeedHttpMessageConverter + * @see RssChannelHttpMessageConverter + */ +public abstract class AbstractWireFeedHttpMessageConverter extends AbstractHttpMessageConverter { + + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + protected AbstractWireFeedHttpMessageConverter(MediaType supportedMediaType) { + super(supportedMediaType); + } + + @Override + @SuppressWarnings("unchecked") + protected T readInternal(Class clazz, HttpInputMessage inputMessage) + throws IOException, HttpMessageNotReadableException { + WireFeedInput feedInput = new WireFeedInput(); + MediaType contentType = inputMessage.getHeaders().getContentType(); + Charset charset; + if (contentType != null && contentType.getCharSet() != null) { + charset = contentType.getCharSet(); + } else { + charset = DEFAULT_CHARSET; + } + try { + Reader reader = new InputStreamReader(inputMessage.getBody(), charset); + return (T) feedInput.build(reader); + } + catch (FeedException ex) { + throw new HttpMessageNotReadableException("Could not read WireFeed: " + ex.getMessage(), ex); + } + } + + @Override + protected void writeInternal(T wireFeed, HttpOutputMessage outputMessage) + throws IOException, HttpMessageNotWritableException { + String wireFeedEncoding = wireFeed.getEncoding(); + if (!StringUtils.hasLength(wireFeedEncoding)) { + wireFeedEncoding = DEFAULT_CHARSET.name(); + } + MediaType contentType = outputMessage.getHeaders().getContentType(); + if (contentType != null) { + Charset wireFeedCharset = Charset.forName(wireFeedEncoding); + contentType = new MediaType(contentType.getType(), contentType.getSubtype(), wireFeedCharset); + outputMessage.getHeaders().setContentType(contentType); + } + + WireFeedOutput feedOutput = new WireFeedOutput(); + + try { + Writer writer = new OutputStreamWriter(outputMessage.getBody(), wireFeedEncoding); + feedOutput.output(wireFeed, writer); + } + catch (FeedException ex) { + throw new HttpMessageNotWritableException("Could not write WiredFeed: " + ex.getMessage(), ex); + } + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverter.java new file mode 100644 index 00000000000..e8a4e43c7bf --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter.feed; + +import com.sun.syndication.feed.atom.Feed; + +import org.springframework.http.MediaType; + +/** + * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write Atom feeds. + * Specifically, this converter can handle {@link Feed} objects, from the ROME + * project. + * + *

By default, this converter reads and writes the media type ({@code application/atom+xml}). This can + * be overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property. + * + * @author Arjen Poutsma + * @see Feed + * @since 3.0.2 + */ +public class AtomFeedHttpMessageConverter extends AbstractWireFeedHttpMessageConverter { + + public AtomFeedHttpMessageConverter() { + super(new MediaType("application", "atom+xml")); + } + + @Override + protected boolean supports(Class clazz) { + return Feed.class.isAssignableFrom(clazz); + } + + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverter.java new file mode 100644 index 00000000000..7add4751ba1 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter.feed; + +import com.sun.syndication.feed.rss.Channel; + +import org.springframework.http.MediaType; + +/** + * Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write RSS feeds. + * Specifically, this converter can handle {@link Channel} objects, from the ROME + * project. + * + *

By default, this converter reads and writes the media type ({@code application/rss+xml}). This can + * be overridden by setting the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property. + * + * @author Arjen Poutsma + * @see Channel + * @since 3.0.2 + */ +public class RssChannelHttpMessageConverter extends AbstractWireFeedHttpMessageConverter { + + public RssChannelHttpMessageConverter() { + super(new MediaType("application", "rss+xml")); + } + + @Override + protected boolean supports(Class clazz) { + return Channel.class.isAssignableFrom(clazz); + } + + +} \ No newline at end of file diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/feed/package-info.java b/org.springframework.web/src/main/java/org/springframework/http/converter/feed/package-info.java new file mode 100644 index 00000000000..919e6eaf1ee --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/feed/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * + * Provides HttpMessageConverter implementations for handling Atom and RSS feeds. + * + */ +package org.springframework.http.converter.feed; + diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java new file mode 100644 index 00000000000..57ffc51f356 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/feed/AtomFeedHttpMessageConverterTests.java @@ -0,0 +1,130 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter.feed; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Feed; +import static org.custommonkey.xmlunit.XMLAssert.*; +import org.custommonkey.xmlunit.XMLUnit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.SAXException; + +import org.springframework.http.MediaType; +import org.springframework.http.MockHttpInputMessage; +import org.springframework.http.MockHttpOutputMessage; + +/** @author Arjen Poutsma */ +public class AtomFeedHttpMessageConverterTests { + + private AtomFeedHttpMessageConverter converter; + + private Charset utf8; + + @Before + public void setUp() { + utf8 = Charset.forName("UTF-8"); + converter = new AtomFeedHttpMessageConverter(); + XMLUnit.setIgnoreWhitespace(true); + } + + @Test + public void canRead() { + assertTrue(converter.canRead(Feed.class, new MediaType("application", "atom+xml"))); + assertTrue(converter.canRead(Feed.class, new MediaType("application", "atom+xml", utf8))); + } + + @Test + public void canWrite() { + assertTrue(converter.canWrite(Feed.class, new MediaType("application", "atom+xml"))); + assertTrue(converter.canWrite(Feed.class, new MediaType("application", "atom+xml", Charset.forName("UTF-8")))); + } + + @Test + public void read() throws IOException { + InputStream is = getClass().getResourceAsStream("atom.xml"); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(is); + inputMessage.getHeaders().setContentType(new MediaType("application", "atom+xml", utf8)); + Feed result = converter.read(Feed.class, inputMessage); + assertEquals("title", result.getTitle()); + assertEquals("subtitle", result.getSubtitle().getValue()); + List entries = result.getEntries(); + assertEquals(2, entries.size()); + + Entry entry1 = (Entry) entries.get(0); + assertEquals("id1", entry1.getId()); + assertEquals("title1", entry1.getTitle()); + + Entry entry2 = (Entry) entries.get(1); + assertEquals("id2", entry2.getId()); + assertEquals("title2", entry2.getTitle()); + } + + @Test + public void write() throws IOException, SAXException { + Feed feed = new Feed("atom_1.0"); + feed.setTitle("title"); + + Entry entry1 = new Entry(); + entry1.setId("id1"); + entry1.setTitle("title1"); + + Entry entry2 = new Entry(); + entry2.setId("id2"); + entry2.setTitle("title2"); + + List entries = new ArrayList(2); + entries.add(entry1); + entries.add(entry2); + feed.setEntries(entries); + + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + converter.write(feed, null, outputMessage); + + assertEquals("Invalid content-type", new MediaType("application", "atom+xml", utf8), + outputMessage.getHeaders().getContentType()); + String expected = "" + "title" + + "id1title1" + + "id2title2"; + assertXMLEqual(expected, outputMessage.getBodyAsString(utf8)); + + } + + @Test + public void writeOtherCharset() throws IOException, SAXException { + Feed feed = new Feed("atom_1.0"); + feed.setTitle("title"); + String encoding = "ISO-8859-1"; + feed.setEncoding(encoding); + + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + converter.write(feed, null, outputMessage); + + assertEquals("Invalid content-type", new MediaType("application", "atom+xml", Charset.forName(encoding)), + outputMessage.getHeaders().getContentType()); + } + + +} diff --git a/org.springframework.web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java b/org.springframework.web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java new file mode 100644 index 00000000000..6f7cf78cd31 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/converter/feed/RssChannelHttpMessageConverterTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.http.converter.feed; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import com.sun.syndication.feed.rss.Channel; +import com.sun.syndication.feed.rss.Item; +import static org.custommonkey.xmlunit.XMLAssert.*; +import org.custommonkey.xmlunit.XMLUnit; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; +import org.junit.Test; +import org.xml.sax.SAXException; + +import org.springframework.http.MediaType; +import org.springframework.http.MockHttpInputMessage; +import org.springframework.http.MockHttpOutputMessage; + +/** @author Arjen Poutsma */ +public class RssChannelHttpMessageConverterTests { + + private RssChannelHttpMessageConverter converter; + + private Charset utf8; + + @Before + public void setUp() { + utf8 = Charset.forName("UTF-8"); + converter = new RssChannelHttpMessageConverter(); + XMLUnit.setIgnoreWhitespace(true); + } + + @Test + public void canRead() { + assertTrue(converter.canRead(Channel.class, new MediaType("application", "rss+xml"))); + assertTrue(converter.canRead(Channel.class, new MediaType("application", "rss+xml", utf8))); + } + + @Test + public void canWrite() { + assertTrue(converter.canWrite(Channel.class, new MediaType("application", "rss+xml"))); + assertTrue(converter.canWrite(Channel.class, new MediaType("application", "rss+xml", Charset.forName("UTF-8")))); + } + + @Test + public void read() throws IOException { + InputStream is = getClass().getResourceAsStream("rss.xml"); + MockHttpInputMessage inputMessage = new MockHttpInputMessage(is); + inputMessage.getHeaders().setContentType(new MediaType("application", "rss+xml", utf8)); + Channel result = converter.read(Channel.class, inputMessage); + assertEquals("title", result.getTitle()); + assertEquals("http://example.com", result.getLink()); + assertEquals("description", result.getDescription()); + + List items = result.getItems(); + assertEquals(2, items.size()); + + Item item1 = (Item) items.get(0); + assertEquals("title1", item1.getTitle()); + + Item item2 = (Item) items.get(1); + assertEquals("title2", item2.getTitle()); + } + + @Test + public void write() throws IOException, SAXException { + Channel channel = new Channel("rss_2.0"); + channel.setTitle("title"); + channel.setLink("http://example.com"); + channel.setDescription("description"); + + Item item1 = new Item(); + item1.setTitle("title1"); + + Item item2 = new Item(); + item2.setTitle("title2"); + + List items = new ArrayList(2); + items.add(item1); + items.add(item2); + channel.setItems(items); + + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + converter.write(channel, null, outputMessage); + + assertEquals("Invalid content-type", new MediaType("application", "rss+xml", utf8), + outputMessage.getHeaders().getContentType()); + String expected = "" + + "titlehttp://example.comdescription" + + "title1" + + "title2" + + ""; + assertXMLEqual(expected, outputMessage.getBodyAsString(utf8)); + + } + + @Test + public void writeOtherCharset() throws IOException, SAXException { + Channel channel = new Channel("rss_2.0"); + channel.setTitle("title"); + channel.setLink("http://example.com"); + channel.setDescription("description"); + + String encoding = "ISO-8859-1"; + channel.setEncoding(encoding); + + Item item1 = new Item(); + item1.setTitle("title1"); + + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + converter.write(channel, null, outputMessage); + + assertEquals("Invalid content-type", new MediaType("application", "rss+xml", Charset.forName(encoding)), + outputMessage.getHeaders().getContentType()); + } + + +} \ No newline at end of file diff --git a/org.springframework.web/src/test/resources/org/springframework/http/converter/feed/atom.xml b/org.springframework.web/src/test/resources/org/springframework/http/converter/feed/atom.xml new file mode 100644 index 00000000000..15b9ed69dbd --- /dev/null +++ b/org.springframework.web/src/test/resources/org/springframework/http/converter/feed/atom.xml @@ -0,0 +1,14 @@ + + + title + subtitle + + id1 + title1 + + + id2 + title2 + + diff --git a/org.springframework.web/src/test/resources/org/springframework/http/converter/feed/rss.xml b/org.springframework.web/src/test/resources/org/springframework/http/converter/feed/rss.xml new file mode 100644 index 00000000000..7f0fe53c578 --- /dev/null +++ b/org.springframework.web/src/test/resources/org/springframework/http/converter/feed/rss.xml @@ -0,0 +1,14 @@ + + + + title + http://example.com + description + + title1 + + + title2 + + + \ No newline at end of file diff --git a/org.springframework.web/template.mf b/org.springframework.web/template.mf index ab7a3e6d534..52fa9ef22ec 100644 --- a/org.springframework.web/template.mf +++ b/org.springframework.web/template.mf @@ -4,6 +4,7 @@ Bundle-Vendor: SpringSource Bundle-ManifestVersion: 2 Import-Template: com.caucho.*;version="[3.2.0, 5.0.0)";resolution:=optional, + com.sun.syndication.*;version="[1.0.0, 2.0.0)";resolution:=optional, org.codehaus.jackson.*;version="[1.0.0, 2.0.0)";resolution:=optional, com.sun.net.*;version="0";resolution:=optional, javax.el.*;version="[1.0.0, 3.0.0)";resolution:=optional,