blob: 6b00f93dcd7f3c2f75b639eb73d5619fc21aa153 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.shindig.social.core.util.xstream;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.shindig.protocol.DataCollection;
import org.apache.shindig.protocol.RestfulCollection;
import org.apache.shindig.protocol.conversion.xstream.ClassFieldMapping;
import org.apache.shindig.protocol.conversion.xstream.DataCollectionConverter;
import org.apache.shindig.protocol.conversion.xstream.GuiceBeanConverter;
import org.apache.shindig.protocol.conversion.xstream.ImplicitCollectionFieldMapping;
import org.apache.shindig.protocol.conversion.xstream.InterfaceClassMapper;
import org.apache.shindig.protocol.conversion.xstream.InterfaceFieldAliasMapping;
import org.apache.shindig.protocol.conversion.xstream.InterfaceFieldAliasingMapper;
import org.apache.shindig.protocol.conversion.xstream.MapConverter;
import org.apache.shindig.protocol.conversion.xstream.NamespaceSet;
import org.apache.shindig.protocol.conversion.xstream.RestfullCollectionConverter;
import org.apache.shindig.protocol.conversion.xstream.WriterStack;
import org.apache.shindig.protocol.conversion.xstream.XStreamConfiguration;
import org.apache.shindig.protocol.model.EnumImpl;
import org.apache.shindig.social.core.util.atom.AtomAttribute;
import org.apache.shindig.social.core.util.atom.AtomAttributeConverter;
import org.apache.shindig.social.core.util.atom.AtomContent;
import org.apache.shindig.social.core.util.atom.AtomEntry;
import org.apache.shindig.social.core.util.atom.AtomFeed;
import org.apache.shindig.social.core.util.atom.AtomKeyValue;
import org.apache.shindig.social.core.util.atom.AtomLinkConverter;
import org.apache.shindig.social.opensocial.model.Account;
import org.apache.shindig.social.opensocial.model.Activity;
import org.apache.shindig.social.opensocial.model.ActivityStream;
import org.apache.shindig.social.opensocial.model.Address;
import org.apache.shindig.social.opensocial.model.BodyType;
import org.apache.shindig.social.opensocial.model.ListField;
import org.apache.shindig.social.opensocial.model.MediaItem;
import org.apache.shindig.social.opensocial.model.Message;
import org.apache.shindig.social.opensocial.model.MessageCollection;
import org.apache.shindig.social.opensocial.model.Name;
import org.apache.shindig.social.opensocial.model.Organization;
import org.apache.shindig.social.opensocial.model.Person;
import org.apache.shindig.social.opensocial.model.Url;
import com.google.common.base.Preconditions;
import com.google.common.collect.ForwardingMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.extended.ISO8601DateConverter;
import com.thoughtworks.xstream.converters.extended.ISO8601GregorianCalendarConverter;
import com.thoughtworks.xstream.converters.extended.ISO8601SqlTimestampConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.mapper.AttributeMapper;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* Opensocial 0.81 compliant Xstream binding
*/
public class XStream081Configuration implements XStreamConfiguration {
/**
* Defines the type of the list container when at the top level where there
* are no methods to specify the name of the list.
*/
private static final Map<ConverterSet, List<ClassFieldMapping>> listElementMappingList =
DefaultedEnumMap.init(ConverterSet.class, ConverterSet.DEFAULT);
/**
* Specifies a priority sorted list of Class to Element Name mappings.
*/
private static final Map<ConverterSet, List<ClassFieldMapping>> elementMappingList =
DefaultedEnumMap.init(ConverterSet.class, ConverterSet.DEFAULT);
/**
* A list of omits, the potential field is the key, and if the class which the
* field is in, is also in the list, the field is supressed.
*/
private static final Map<ConverterSet, ImmutableMultimap<String, Class<?>>> omitMap = ImmutableMap.of(ConverterSet.DEFAULT,
ImmutableMultimap.<String,Class<?>>builder()
.put("isOwner", Person.class)
.put("isViewer", Person.class).build());
/**
* Maps elements names to classes.
*/
private static final Map<ConverterSet, Map<String, Class<?>>> elementClassMap = DefaultedEnumMap.init(ConverterSet.class, ConverterSet.DEFAULT);
private static final Map<ConverterSet, List<ImplicitCollectionFieldMapping>> itemFieldMappings = DefaultedEnumMap.init(ConverterSet.class, ConverterSet.DEFAULT);
private static final Map<ConverterSet, List<InterfaceFieldAliasMapping>> fieldAliasMappingList = DefaultedEnumMap.init(ConverterSet.class, ConverterSet.DEFAULT);
private static final String ATOM_NS = "http://www.w3.org/2005/Atom";
private static final String OS_NS = "http://ns.opensocial.org/2008/opensocial";
private static final String OSEARCH_NS = "http://a9.com/-/spec/opensearch/1.1";
private static final Map<String, NamespaceSet> NAMESPACES = initNameSpace();
private static Map<String, NamespaceSet> initNameSpace() {
// configure the name space mapping. This does not need to be all the elments in the
// namespace, just the point of translation from one namespace to another.
// It would have been good to use a standard parser/serializer approach, but
// the xstream namespace implementation does not work exactly how we need it to.
NamespaceSet atom = new NamespaceSet();
atom.addNamespace("xmlns", ATOM_NS);
atom.addNamespace("xmlns:osearch", OSEARCH_NS);
atom.addPrefixedElement("totalResults", "osearch:totalResults");
atom.addPrefixedElement("startIndex", "osearch:startIndex");
atom.addPrefixedElement("itemsPerPage", "osearch:itemsPerPage");
NamespaceSet os = new NamespaceSet();
os.addNamespace("xmlns", OS_NS);
return ImmutableMap.<String, NamespaceSet>builder()
.put("feed", atom)
.put("person", os)
.put("activity", os)
.put("activityStream", os)
.put("account", os)
.put("address", os)
.put("bodyType", os)
.put("message", os)
.put("mediaItem", os)
.put("name", os)
.put("url", os)
.put("response", os)
.put("appdata", os)
.build();
}
static {
elementMappingList.put(ConverterSet.DEFAULT, ImmutableList.of(
// this is order specific, so put the more specified interfaces at the top.
new ClassFieldMapping("feed", AtomFeed.class),
new ClassFieldMapping("content", AtomContent.class),
new ClassFieldMapping("activity", Activity.class),
new ClassFieldMapping("activityStream", ActivityStream.class),
new ClassFieldMapping("account", Account.class),
new ClassFieldMapping("address", Address.class),
new ClassFieldMapping("bodyType", BodyType.class),
new ClassFieldMapping("message", Message.class),
new ClassFieldMapping("messageCollection", MessageCollection.class),
new ClassFieldMapping("mediaItem", MediaItem.class),
new ClassFieldMapping("name", Name.class),
new ClassFieldMapping("organization", Organization.class),
new ClassFieldMapping("person", Person.class),
new ClassFieldMapping("url", Url.class),
// this is an example of a class field mapping with context. If
// ListField is mapped inside an element named emails, replace the element
// name
// that would have been defiend as fqcn ListField with email
new ClassFieldMapping("ListField", ListField.class),
// some standard mappings not needed for runtime, but used in test, at the
// bottom so as not
// to conflict with other mappings.
new ClassFieldMapping("response", RestfulCollection.class),
new ClassFieldMapping("appdata", DataCollection.class),
new ClassFieldMapping("list", List.class),
new ClassFieldMapping("map", Map.class))
);
// element setup for RestfullCollection Responses
elementMappingList.put(ConverterSet.COLLECTION, ImmutableList.of(
new ClassFieldMapping("feed", AtomFeed.class),
new ClassFieldMapping("content", AtomContent.class),
new ClassFieldMapping("activity", Activity.class),
new ClassFieldMapping("activityStream", ActivityStream.class),
new ClassFieldMapping("account", Account.class),
new ClassFieldMapping("address", Address.class),
new ClassFieldMapping("bodyType", BodyType.class),
new ClassFieldMapping("message", Message.class),
new ClassFieldMapping("messageCollection", MessageCollection.class),
new ClassFieldMapping("mediaItem", MediaItem.class),
new ClassFieldMapping("name", Name.class),
new ClassFieldMapping("organization", Organization.class),
new ClassFieldMapping("person", Person.class),
new ClassFieldMapping("url", Url.class),
// this is an example of a class field mapping with context. If
// ListField is mapped inside an element named emails, replace the element
// name that would have been defiend as fqcn ListField with email
// new ClassFieldMapping("emails", "email", ListField.class),
// new ClassFieldMapping("phoneNumbers","phone", ListField.class),
new ClassFieldMapping("ListField", ListField.class),
// some standard mappings not needed for runtime, but used in test, at the
// bottom so as not to conflict with other mappings.
new ClassFieldMapping("response", RestfulCollection.class),
new ClassFieldMapping("list", List.class),
new ClassFieldMapping("map", Map.class))
);
elementClassMap.put(ConverterSet.DEFAULT, new ImmutableMap.Builder<String, Class<?>>()
.put("feed", AtomFeed.class)
.put("content", AtomContent.class)
.put("email", ListField.class)
.put("phone", ListField.class)
.put("list", ArrayList.class)
.put("map", ConcurrentHashMap.class)
.put("appdata", DataCollection.class)
.put("activity", Activity.class)
.put("activityStream", ActivityStream.class)
.put("account", Account.class)
.put("address", Address.class)
.put("bodyType", BodyType.class)
.put("message", Message.class)
.put("messageCollection", MessageCollection.class)
.put("mediaItem", MediaItem.class)
.put("name", Name.class)
.put("organization", Organization.class)
.put("person", Person.class)
.put("url", Url.class)
.put("listField", ListField.class).build()
);
itemFieldMappings.put(ConverterSet.DEFAULT, ImmutableList.of(
new ImplicitCollectionFieldMapping(AtomFeed.class, "entry", AtomEntry.class, "entry"),
new ImplicitCollectionFieldMapping(AtomContent.class, "entry", AtomKeyValue.class, "entry"),
new ImplicitCollectionFieldMapping(Person.class, "books", String.class, "books"),
new ImplicitCollectionFieldMapping(Person.class, "cars", String.class, "cars"),
new ImplicitCollectionFieldMapping(Person.class, "heroes", String.class, "heroes"),
new ImplicitCollectionFieldMapping(Person.class, "food", String.class, "food"),
new ImplicitCollectionFieldMapping(Person.class, "interests", String.class, "interests"),
new ImplicitCollectionFieldMapping(Person.class, "languagesSpoken", String.class, "languagesSpoken"),
new ImplicitCollectionFieldMapping(Person.class, "movies", String.class, "movies"),
new ImplicitCollectionFieldMapping(Person.class, "music", String.class, "music"),
new ImplicitCollectionFieldMapping(Person.class, "quotes", String.class, "quotes"),
new ImplicitCollectionFieldMapping(Person.class, "sports", String.class, "sports"),
new ImplicitCollectionFieldMapping(Person.class, "tags", String.class, "tags"),
new ImplicitCollectionFieldMapping(Person.class, "turnOns", String.class, "turnOns"),
new ImplicitCollectionFieldMapping(Person.class, "turnOffs", String.class, "turnOffs"),
new ImplicitCollectionFieldMapping(Person.class, "tvShows", String.class, "tvShows"),
new ImplicitCollectionFieldMapping(Person.class, "emails", ListField.class, "emails"),
new ImplicitCollectionFieldMapping(Person.class, "phoneNumbers", ListField.class, "phoneNumbers"),
new ImplicitCollectionFieldMapping(Person.class, "ims", ListField.class, "ims"),
new ImplicitCollectionFieldMapping(Person.class, "photos", ListField.class, "photos"),
new ImplicitCollectionFieldMapping(Person.class, "activities", Activity.class, "activities"),
new ImplicitCollectionFieldMapping(Person.class, "addresses", Address.class, "addresses"),
new ImplicitCollectionFieldMapping(Person.class, "organizations", Organization.class, "organizations"),
new ImplicitCollectionFieldMapping(Person.class, "urls", Url.class, "urls"),
new ImplicitCollectionFieldMapping(Person.class, "lookingFor", EnumImpl.class, "lookingFor"),
new ImplicitCollectionFieldMapping(Message.class, "recipients", String.class, "recipients"),
new ImplicitCollectionFieldMapping(Message.class, "collectionIds", String.class, "collectionsIds"),
new ImplicitCollectionFieldMapping(Message.class, "replies", String.class, "replies"),
new ImplicitCollectionFieldMapping(Activity.class, "mediaItems", MediaItem.class, "mediaItems"))
);
listElementMappingList.put(ConverterSet.DEFAULT, ImmutableList.<ClassFieldMapping>of());
fieldAliasMappingList.put(ConverterSet.DEFAULT, ImmutableList.<InterfaceFieldAliasMapping>of());
}
/**
* The Guice injector, used for creating new instances of the model.
*/
private Injector injector;
/**
* @param injector the injector to initialize with
*/
@Inject
public XStream081Configuration(Injector injector) {
this.injector = injector;
}
private static Multimap<String, Class<?>> getOmitMap(ConverterSet c) {
return firstNonNull(omitMap.get(c), omitMap.get(ConverterSet.DEFAULT));
}
/**
* {@inheritDoc}
*
* @param writerStack
* @see XStreamConfiguration#getConverterConfig(org.apache.shindig.protocol.conversion.xstream.XStreamConfiguration.ConverterSet, com.thoughtworks.xstream.converters.reflection.ReflectionProvider, com.thoughtworks.xstream.mapper.Mapper, com.thoughtworks.xstream.io.HierarchicalStreamDriver, org.apache.shindig.protocol.conversion.xstream.WriterStack)
*/
public ConverterConfig getConverterConfig(ConverterSet c, ReflectionProvider rp,
Mapper dmapper, HierarchicalStreamDriver driver, WriterStack writerStack) {
InterfaceFieldAliasingMapper emapper = new InterfaceFieldAliasingMapper(dmapper, writerStack, fieldAliasMappingList.get(c));
InterfaceClassMapper fmapper = new InterfaceClassMapper(writerStack,
emapper,
elementMappingList.get(c),
listElementMappingList.get(c),
itemFieldMappings.get(c),
getOmitMap(c),
elementClassMap.get(c));
AttributeMapper amapper = new AttributeMapper(fmapper);
XStream xstream = new XStream(rp, amapper, driver);
xstream.registerConverter(new MapConverter(fmapper));
xstream.registerConverter(new RestfullCollectionConverter(fmapper));
xstream.registerConverter(new DataCollectionConverter(fmapper));
xstream.registerConverter(new AtomLinkConverter());
xstream.registerConverter(new ISO8601DateConverter());
xstream.registerConverter(new ISO8601GregorianCalendarConverter());
xstream.registerConverter(new ISO8601SqlTimestampConverter());
xstream.registerConverter(new GuiceBeanConverter(fmapper, injector));
xstream.registerConverter(new AtomAttributeConverter());
xstream.setMode(XStream.NO_REFERENCES);
amapper.addAttributeFor(AtomAttribute.class);
// prevent NPE on xstream 1.3.x
amapper.setConverterLookup(xstream.getConverterLookup());
return new ConverterConfig(fmapper, xstream);
}
/**
* {@inheritDoc}
*
* @see org.apache.shindig.protocol.conversion.xstream.XStreamConfiguration#getNameSpaces()
*/
public Map<String, NamespaceSet> getNameSpaces() {
return NAMESPACES;
}
/**
* Delegate for an EnumMap that returns the value of the defaultkey if the
* designated key is not present.
* @param <K>
* @param <V>
*/
private static final class DefaultedEnumMap<K extends Enum<K>,V> extends ForwardingMap<K,V> {
private final EnumMap<K,V> backing;
final K defaultval;
public DefaultedEnumMap(Class<K> clz, K defaultkey) {
this.backing = new EnumMap<K,V>(Preconditions.checkNotNull(clz));
this.defaultval = Preconditions.checkNotNull(defaultkey);
}
public static <K extends Enum<K>,V> DefaultedEnumMap<K,V> init(Class<K> clz, K defaultkey) {
return new DefaultedEnumMap<K,V>(clz, defaultkey);
}
@Override
@SuppressWarnings("unchecked")
public V get(Object o) {
K key = (K)o;
return firstNonNull(backing.get(key), backing.get(defaultval));
}
@Override
protected Map<K,V> delegate() {
return backing;
}
}
public static <T> T firstNonNull(T first, T second) {
return first != null ? first : Preconditions.checkNotNull(second);
}
}