/* Copyright (c) 2008 Google Inc.
*
* 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 com.google.gdata.model.transforms.atom;
import static com.google.gdata.model.MetadataContext.RSS;
import com.google.common.collect.Lists;
import com.google.gdata.util.common.xml.XmlNamespace;
import com.google.gdata.util.common.xml.XmlWriter;
import com.google.gdata.util.common.xml.XmlWriter.Attribute;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.ILink.Rel;
import com.google.gdata.model.Element;
import com.google.gdata.model.ElementMetadata;
import com.google.gdata.model.MetadataRegistry;
import com.google.gdata.model.MetadataValueTransform;
import com.google.gdata.model.QName;
import com.google.gdata.model.Metadata.VirtualValue;
import com.google.gdata.model.atom.Author;
import com.google.gdata.model.atom.Category;
import com.google.gdata.model.atom.Content;
import com.google.gdata.model.atom.Contributor;
import com.google.gdata.model.atom.Entry;
import com.google.gdata.model.atom.Feed;
import com.google.gdata.model.atom.Link;
import com.google.gdata.model.atom.OutOfLineContent;
import com.google.gdata.model.atom.Person;
import com.google.gdata.model.atom.Source;
import com.google.gdata.model.atom.TextContent;
import com.google.gdata.model.atom.Source.Generator;
import com.google.gdata.model.atompub.Edited;
import com.google.gdata.model.gd.GdAttributes;
import com.google.gdata.util.ContentType;
import com.google.gdata.util.Namespaces;
import com.google.gdata.util.ParseException;
import com.google.gdata.wireformats.XmlGenerator;
import com.google.gdata.wireformats.XmlWireFormatProperties;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* RSS transforms for atom. This contains the transforms required to output
* atom classes in RSS using the new data model.
*
*
*/
public class AtomRssTransforms {
// Constants for our rss names.
private static final QName DOMAIN = new QName("domain");
private static final QName CATEGORY = new QName(Namespaces.rssNs, "category");
private static final QName ITEM = new QName(Namespaces.rssNs, "item");
private static final QName GUID = new QName(Namespaces.rssNs, "guid");
private static final QName TITLE = new QName(Namespaces.rssNs, "title");
private static final QName PUB_DATE = new QName(Namespaces.rssNs, "pubDate");
private static final QName AUTHOR = new QName(Namespaces.rssNs, "author");
private static final QName RSS_NAME = new QName(Namespaces.rssNs, "rss");
private static final QName DESCRIPTION =
new QName(Namespaces.rssNs, "description");
private static final QName LAST_BUILD_DATE =
new QName(Namespaces.rssNs, "lastBuildDate");
private static final QName COPYRIGHT =
new QName(Namespaces.rssNs, "copyright");
private static final QName MANAGING_EDITOR =
new QName(Namespaces.rssNs, "managingEditor");
private static final QName GENERATOR =
new QName(Namespaces.rssNs, "generator");
/**
* Add the RSS transforms to the default metadata trees.
*/
public static void addTransforms(MetadataRegistry registry) {
addCategoryTransforms(registry);
addContentTransforms(registry);
addEntryTransforms(registry);
addEntryOutOfLineContentTransforms(registry);
addFeedTransforms(registry);
addLinkTransforms(registry);
addSourceTransforms(registry);
addPersonTransforms(registry);
addGeneratorTransforms(registry);
addAppEditedTransforms(registry);
}
private static void addCategoryTransforms(MetadataRegistry registry) {
registry.build(Category.KEY, RSS)
.setName(CATEGORY)
.setVirtualValue(
new MetadataValueTransform(Category.TERM, Category.LABEL));
registry.build(Category.KEY, Category.SCHEME, RSS)
.setName(DOMAIN);
registry.build(Category.KEY, Category.LABEL, RSS)
.setVisible(false);
registry.build(Category.KEY, Category.TERM, RSS)
.setVisible(false);
}
private static void addContentTransforms(MetadataRegistry registry) {
registry.build(TextContent.KEY, RSS)
.setName(DESCRIPTION)
.whitelistAttributes();
registry.build(TextContent.CONSTRUCT, RSS)
.whitelistAttributes();
}
private static void addEntryTransforms(MetadataRegistry registry) {
registry.build(Entry.KEY, RSS).setName(ITEM);
registry.build(Entry.KEY, GdAttributes.ETAG, RSS)
.setVisible(false);
XmlWireFormatProperties properties = new XmlWireFormatProperties();
properties.setElementGenerator(new XmlGenerator.XmlElementGenerator() {
@Override
protected List<Attribute> getAttributes(Element e,
ElementMetadata<?, ?> metadata) {
List<Attribute> attrs = super.getAttributes(e, metadata);
if (attrs == null) {
attrs = Lists.newArrayListWithExpectedSize(1);
}
attrs.add(new Attribute("isPermaLink", "false"));
return attrs;
}
});
registry.build(Entry.KEY, Entry.ID, RSS)
.setName(GUID)
.setProperties(properties);
registry.build(Entry.KEY, Entry.TITLE, RSS)
.setName(TITLE);
registry.build(Entry.KEY, Entry.PUBLISHED, RSS)
.setName(PUB_DATE)
.setVirtualValue(new VirtualValue() {
public Object generate(Element element,
ElementMetadata<?, ?> metadata) {
DateTime date = element.getTextValue(Entry.PUBLISHED);
return date == null ? "" : date.toStringRfc822();
}
public void parse(Element element, ElementMetadata<?, ?> metadata,
Object value) throws ParseException {
DateTime parsed = DateTime.parseRfc822(value.toString());
element.setTextValue(parsed);
}
});
XmlWireFormatProperties personProperties = new XmlWireFormatProperties();
personProperties.setElementGenerator(
new XmlGenerator.XmlElementGenerator() {
@Override
public boolean startElement(XmlWriter xw, Element parent, Element e,
ElementMetadata<?, ?> metadata) throws IOException {
if (!(e instanceof Person)) {
return super.startElement(xw, parent, e, metadata);
}
Person person = (Person) e;
String email = person.getEmail();
String name = person.getName();
StringBuilder text = new StringBuilder();
boolean hasEmail = email != null;
if (hasEmail) {
text.append(email);
}
if (name != null) {
if (hasEmail) {
text.append(" (");
}
text.append(name);
if (hasEmail) {
text.append(")");
}
}
QName xmlName = getName(e, metadata);
xw.simpleElement(xmlName.getNs(), xmlName.getLocalName(),
null, text.toString());
return false;
}
@Override
public void textContent(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
@Override
public void endElement(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
});
registry.build(Entry.KEY, Author.KEY, RSS)
.setName(AUTHOR)
.setProperties(personProperties);
registry.build(Entry.KEY, Contributor.KEY, RSS)
.setName(AUTHOR)
.setProperties(personProperties);
registry.build(Entry.KEY, Entry.RIGHTS, RSS).setVisible(false);
}
private static void addEntryOutOfLineContentTransforms(
MetadataRegistry registry) {
XmlWireFormatProperties properties = new XmlWireFormatProperties();
properties.setElementGenerator(new XmlGenerator.XmlElementGenerator() {
@Override
public boolean startElement(XmlWriter xw, Element parent, Element e,
ElementMetadata<?, ?> metadata) throws IOException {
if (!(e instanceof OutOfLineContent)) {
return super.startElement(xw, parent, e, metadata);
}
OutOfLineContent content = (OutOfLineContent) e;
ContentType type = content.getMimeType();
URI src = content.getSrc();
generateEnclosure(xw,
type == null ? null : type.getMediaType(),
src == null ? null : src.toString(),
content.getLength());
return false;
}
@Override
public void textContent(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
@Override
public void endElement(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
});
registry.build(OutOfLineContent.KEY, RSS)
.setProperties(properties);
}
private static void addFeedTransforms(final MetadataRegistry registry) {
registry.build(Feed.KEY, GdAttributes.ETAG, RSS)
.setVisible(false);
XmlWireFormatProperties properties = new XmlWireFormatProperties();
properties.setElementGenerator(new XmlGenerator.XmlElementGenerator() {
@Override
public boolean startElement(XmlWriter xw, Element parent, Element e,
ElementMetadata<?, ?> metadata) throws IOException {
Collection<XmlNamespace> namespaces =
getNamespaces(parent, e, metadata);
List<XmlWriter.Attribute> attrs = getAttributes(e, metadata);
if (attrs == null) {
attrs = Lists.newArrayList();
}
attrs.add(new Attribute("version", "2.0"));
xw.startElement(Namespaces.rssNs, "rss", attrs, namespaces);
xw.startElement(Namespaces.rssNs, "channel", null, null);
if (!e.hasElement(Source.SUBTITLE) && !e.hasElement(Content.KEY)) {
// To preserve strict RSS2 compliance and backward compatibility:
// since atom feeds do not require a subtitle or text content, put in
// an empty description into RSS. This keeps RSS2 validators happy
xw.simpleElement(Namespaces.rssNs, "description", null, "");
}
return true;
}
@Override
public void endElement(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) throws IOException {
xw.endElement(Namespaces.rssNs, "channel");
super.endElement(xw, e, metadata);
}
});
registry.build(Feed.KEY, RSS)
.setName(RSS_NAME)
.setProperties(properties);
registry.build(Feed.KEY, Feed.UPDATED, RSS)
.setName(LAST_BUILD_DATE)
.setVirtualValue(new VirtualValue() {
public Object generate(Element element,
ElementMetadata<?, ?> metadata) {
DateTime date = element.getTextValue(Feed.UPDATED);
return date == null ? "" : date.toStringRfc822();
}
public void parse(Element element, ElementMetadata<?, ?> metadata,
Object value) throws ParseException {
DateTime parsed = DateTime.parseRfc822(value.toString());
element.setTextValue(parsed);
}
});
}
private static void addLinkTransforms(MetadataRegistry registry) {
XmlWireFormatProperties properties = new XmlWireFormatProperties();
properties.setElementGenerator(new XmlGenerator.XmlElementGenerator() {
@Override
public boolean startElement(XmlWriter xw, Element parent, Element e,
ElementMetadata<?, ?> metadata) throws IOException {
if (!(e instanceof Link)) {
return super.startElement(xw, parent, e, metadata);
}
Link link = (Link) e;
String rel = link.getRel();
String type = link.getType();
String href = link.getHref();
long length = link.getLength();
if (rel != null && rel.equals("enclosure")) {
generateEnclosure(xw, type, href, length);
} else if ("comments".equals(rel)) {
xw.simpleElement(Namespaces.rssNs, "comments", null, href);
} else if (Rel.ALTERNATE.equals(rel)) {
xw.simpleElement(Namespaces.rssNs, "link", null, href);
} else if (Rel.VIA.equals(rel)) {
if (href != null) {
List<XmlWriter.Attribute> attrs =
Collections.singletonList(new XmlWriter.Attribute("url", href));
xw.simpleElement(Namespaces.rssNs, "source", attrs, null);
}
}
return false;
}
@Override
public void textContent(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
@Override
public void endElement(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
});
registry.build(Link.KEY, RSS)
.setProperties(properties);
}
private static void addSourceTransforms(MetadataRegistry registry) {
registry.build(Source.CONSTRUCT, Source.TITLE, RSS).setName(TITLE);
registry.build(Source.CONSTRUCT, Source.SUBTITLE, RSS).setName(DESCRIPTION);
XmlWireFormatProperties properties = new XmlWireFormatProperties();
properties.setElementGenerator(new XmlGenerator.XmlElementGenerator() {
@Override
public boolean startElement(XmlWriter xw, Element parent, Element e,
ElementMetadata<?, ?> metadata) throws IOException {
boolean isIcon = e.getElementId().equals(Source.ICON.getId());
boolean isLogo = e.getElementId().equals(Source.LOGO.getId());
if ((!isIcon && !isLogo) || !(parent instanceof Source)) {
return super.startElement(xw, parent, e, metadata);
}
Source source = (Source) parent;
if (isIcon && source.hasElement(Source.LOGO)) {
// atom:logo takes precedence
return false;
}
xw.startElement(Namespaces.rssNs, "image", null, null);
xw.simpleElement(Namespaces.rssNs, "url", null,
String.valueOf(e.getTextValue()));
TextContent title = source.getTitle();
if (title != null) {
xw.simpleElement(Namespaces.rssNs, "title", null,
title.getPlainText());
}
Link htmlLink = source.getHtmlLink();
if (htmlLink != null) {
xw.simpleElement(Namespaces.rssNs, "link", null, htmlLink.getHref());
}
xw.endElement(Namespaces.rssNs, "image");
return false;
}
@Override
public void textContent(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
@Override
public void endElement(XmlWriter xw, Element e,
ElementMetadata<?, ?> metadata) {}
});
registry.build(Source.CONSTRUCT, Source.ICON, RSS)
.setProperties(properties);
registry.build(Source.CONSTRUCT, Source.LOGO, RSS)
.setProperties(properties);
registry.build(Source.CONSTRUCT, Source.RIGHTS, RSS)
.setVisible(true)
.setName(COPYRIGHT);
registry.build(Source.CONSTRUCT, Author.KEY, RSS)
.setName(MANAGING_EDITOR)
.setVirtualValue(new MetadataValueTransform(Person.NAME));
}
private static void addPersonTransforms(MetadataRegistry registry) {
registry.build(Person.KEY, Person.EMAIL, RSS)
.setVisible(false);
registry.build(Person.KEY, Person.NAME, RSS)
.setVisible(false);
registry.build(Person.KEY, Person.URI, RSS)
.setVisible(false);
}
private static void addGeneratorTransforms(MetadataRegistry registry) {
registry.build(Generator.KEY, RSS)
.setName(GENERATOR);
registry.build(Generator.KEY, Generator.URI, RSS)
.setVisible(false);
registry.build(Generator.KEY, Generator.VERSION, RSS)
.setVisible(false);
}
private static void addAppEditedTransforms(MetadataRegistry registry) {
registry.build(Edited.KEY, RSS).setVisible(false);
}
private static void generateEnclosure(XmlWriter xw, String type, String href,
long length) throws IOException {
List<XmlWriter.Attribute> attrs =
new ArrayList<XmlWriter.Attribute>(3);
if (type != null) {
attrs.add(new XmlWriter.Attribute("type", type));
}
if (href != null) {
attrs.add(new XmlWriter.Attribute("url", href));
}
// To preserve strict RSS2 compliance and backward compatibility: Since RSS
// requires a length, then put it length=0 when the length is unknown. This
// keeps RSS2 validators happy
attrs.add(new XmlWriter.Attribute(
"length", Long.toString(length == -1 ? 0 : length)));
xw.simpleElement(Namespaces.rssNs, "enclosure", attrs, null);
}
private AtomRssTransforms() {}
}