| /* |
| * 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); |
| } |
| } |