001/* 002 * Copyright 2010-2013 Ning, Inc. 003 * 004 * Ning licenses this file to you under the Apache License, version 2.0 005 * (the "License"); you may not use this file except in compliance with the 006 * License. You may obtain a copy of the License at: 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations 014 * under the License. 015 */ 016 017package com.ning.billing.recurly.model; 018 019import java.util.Arrays; 020import java.util.List; 021import java.util.Map; 022 023import javax.annotation.Nullable; 024import javax.xml.bind.annotation.XmlTransient; 025 026import org.joda.time.DateTime; 027 028import com.ning.billing.recurly.RecurlyClient; 029import com.ning.billing.recurly.model.jackson.RecurlyObjectsSerializer; 030 031import com.fasterxml.jackson.annotation.JsonIgnore; 032import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 033import com.fasterxml.jackson.core.Version; 034import com.fasterxml.jackson.databind.AnnotationIntrospector; 035import com.fasterxml.jackson.databind.SerializationFeature; 036import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair; 037import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; 038import com.fasterxml.jackson.databind.module.SimpleModule; 039import com.fasterxml.jackson.databind.type.TypeFactory; 040import com.fasterxml.jackson.dataformat.xml.XmlMapper; 041import com.fasterxml.jackson.datatype.joda.JodaModule; 042import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; 043 044@JsonIgnoreProperties(ignoreUnknown = true) 045public abstract class RecurlyObject { 046 047 @XmlTransient 048 private RecurlyClient recurlyClient; 049 050 @XmlTransient 051 protected String href; 052 053 public static final String NIL_STR = "nil"; 054 public static final List<String> NIL_VAL = Arrays.asList("nil", "true"); 055 056 // See https://github.com/killbilling/recurly-java-library/issues/4 for why 057 // @JsonIgnore is required here and @XmlTransient is not enough 058 @JsonIgnore 059 public String getHref() { 060 return href; 061 } 062 063 public void setHref(final Object href) { 064 this.href = stringOrNull(href); 065 } 066 067 public static XmlMapper newXmlMapper() { 068 final XmlMapper xmlMapper = new XmlMapper(); 069 final AnnotationIntrospector primary = new JacksonAnnotationIntrospector(); 070 final AnnotationIntrospector secondary = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()); 071 final AnnotationIntrospector pair = new AnnotationIntrospectorPair(primary, secondary); 072 xmlMapper.setAnnotationIntrospector(pair); 073 xmlMapper.registerModule(new JodaModule()); 074 xmlMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 075 076 final SimpleModule m = new SimpleModule("module", new Version(1, 0, 0, null, null, null)); 077 m.addSerializer(Accounts.class, new RecurlyObjectsSerializer<Accounts, Account>(Accounts.class, "account")); 078 m.addSerializer(AddOns.class, new RecurlyObjectsSerializer<AddOns, AddOn>(AddOns.class, "add_on")); 079 m.addSerializer(Adjustments.class, new RecurlyObjectsSerializer<Adjustments, Adjustment>(Adjustments.class, "adjustment")); 080 m.addSerializer(Coupons.class, new RecurlyObjectsSerializer<Coupons, Coupon>(Coupons.class, "coupon")); 081 m.addSerializer(Invoices.class, new RecurlyObjectsSerializer<Invoices, Invoice>(Invoices.class, "invoice")); 082 m.addSerializer(Plans.class, new RecurlyObjectsSerializer<Plans, Plan>(Plans.class, "plan")); 083 m.addSerializer(RecurlyErrors.class, new RecurlyObjectsSerializer<RecurlyErrors, RecurlyError>(RecurlyErrors.class, "error")); 084 m.addSerializer(SubscriptionAddOns.class, new RecurlyObjectsSerializer<SubscriptionAddOns, SubscriptionAddOn>(SubscriptionAddOns.class, "subscription_add_on")); 085 m.addSerializer(Subscriptions.class, new RecurlyObjectsSerializer<Subscriptions, Subscription>(Subscriptions.class, "subscription")); 086 m.addSerializer(Transactions.class, new RecurlyObjectsSerializer<Transactions, Transaction>(Transactions.class, "transaction")); 087 xmlMapper.registerModule(m); 088 089 return xmlMapper; 090 } 091 092 public static Boolean booleanOrNull(@Nullable final Object object) { 093 if (isNull(object)) { 094 return null; 095 } 096 097 // Booleans are represented as objects (e.g. <display_quantity type="boolean">false</display_quantity>), which Jackson 098 // will interpret as an Object (Map), not Booleans. 099 if (object instanceof Map) { 100 final Map map = (Map) object; 101 if (map.keySet().size() == 2 && "boolean".equals(map.get("type"))) { 102 return Boolean.valueOf((String) map.get("")); 103 } 104 } 105 106 return Boolean.valueOf(object.toString()); 107 } 108 109 public static String stringOrNull(@Nullable final Object object) { 110 if (isNull(object)) { 111 return null; 112 } 113 114 return object.toString().trim(); 115 } 116 117 public static Integer integerOrNull(@Nullable final Object object) { 118 if (isNull(object)) { 119 return null; 120 } 121 122 // Integers are represented as objects (e.g. <year type="integer">2015</year>), which Jackson 123 // will interpret as an Object (Map), not Integers. 124 if (object instanceof Map) { 125 final Map map = (Map) object; 126 if (map.keySet().size() == 2 && "integer".equals(map.get("type"))) { 127 return Integer.valueOf((String) map.get("")); 128 } 129 } 130 131 return Integer.valueOf(object.toString()); 132 } 133 134 public static DateTime dateTimeOrNull(@Nullable final Object object) { 135 if (isNull(object)) { 136 return null; 137 } 138 139 // DateTimes are represented as objects (e.g. <created_at type="datetime">2011-04-19T07:00:00Z</created_at>), which Jackson 140 // will interpret as an Object (Map), not DateTimes. 141 if (object instanceof Map) { 142 final Map map = (Map) object; 143 if (map.keySet().size() == 2 && "datetime".equals(map.get("type"))) { 144 return new DateTime(map.get("")); 145 } 146 } 147 148 return new DateTime(object.toString()); 149 } 150 151 public static boolean isNull(@Nullable final Object object) { 152 if (object == null) { 153 return true; 154 } 155 156 // Hack to work around Recurly output for nil values: the response will contain 157 // an element with a nil attribute (e.g. <city nil="nil"></city> or <username nil="true"></username>) which Jackson will 158 // interpret as an Object (Map), not a String. 159 if (object instanceof Map) { 160 final Map map = (Map) object; 161 if (map.keySet().size() >= 1 && map.get(NIL_STR) != null && NIL_VAL.contains(map.get(NIL_STR).toString())) { 162 return true; 163 } 164 } 165 166 return false; 167 } 168 169 <T extends RecurlyObject> T fetch(final T object, final Class<T> clazz) { 170 if (object.getHref() == null || recurlyClient == null) { 171 return object; 172 } 173 return recurlyClient.doGETWithFullURL(clazz, object.getHref()); 174 } 175 176 public void setRecurlyClient(final RecurlyClient recurlyClient) { 177 this.recurlyClient = recurlyClient; 178 } 179}