killbill-uncached
Changes
payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java 3(+2 -1)
payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java 80(+80 -0)
profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java 69(+66 -3)
util/pom.xml 4(+4 -0)
Details
diff --git a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
index 3bd4afc..03885a9 100644
--- a/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
+++ b/account/src/main/java/org/killbill/billing/account/api/DefaultImmutableAccountData.java
@@ -17,28 +17,40 @@
package org.killbill.billing.account.api;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.util.account.AccountDateTimeUtils;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
-public class DefaultImmutableAccountData implements ImmutableAccountData {
+public class DefaultImmutableAccountData implements ImmutableAccountData, Externalizable {
- private final UUID id;
- private final String externalKey;
- private final Currency currency;
- private final DateTimeZone dateTimeZone;
- private final DateTimeZone fixedOffsetDateTimeZone;
- private final DateTime referenceTime;
+ private static final long serialVersionUID = 8117686452347277415L;
- public DefaultImmutableAccountData(final UUID id, final String externalKey, final Currency currency, final DateTimeZone dateTimeZone, final DateTimeZone fixedOffsetDateTimeZone, final DateTime referenceTime) {
+ private UUID id;
+ private String externalKey;
+ private Currency currency;
+ private DateTimeZone timeZone;
+ private DateTimeZone fixedOffsetTimeZone;
+ private DateTime referenceTime;
+
+ // For deserialization
+ public DefaultImmutableAccountData() {}
+
+ public DefaultImmutableAccountData(final UUID id, final String externalKey, final Currency currency, final DateTimeZone timeZone, final DateTimeZone fixedOffsetTimeZone, final DateTime referenceTime) {
this.id = id;
this.externalKey = externalKey;
this.currency = currency;
- this.dateTimeZone = dateTimeZone;
- this.fixedOffsetDateTimeZone = fixedOffsetDateTimeZone;
+ this.timeZone = timeZone;
+ this.fixedOffsetTimeZone = fixedOffsetTimeZone;
this.referenceTime = referenceTime;
}
@@ -68,7 +80,7 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
@Override
public DateTimeZone getTimeZone() {
- return dateTimeZone;
+ return timeZone;
}
@Override
@@ -86,7 +98,7 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
}
public DateTimeZone getFixedOffsetTimeZone() {
- return fixedOffsetDateTimeZone;
+ return fixedOffsetTimeZone;
}
@Override
@@ -100,8 +112,8 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
sb.append("id=").append(id);
sb.append(", externalKey='").append(externalKey).append('\'');
sb.append(", currency=").append(currency);
- sb.append(", dateTimeZone=").append(dateTimeZone);
- sb.append(", fixedOffsetDateTimeZone=").append(fixedOffsetDateTimeZone);
+ sb.append(", timeZone=").append(timeZone);
+ sb.append(", fixedOffsetTimeZone=").append(fixedOffsetTimeZone);
sb.append(", referenceTime=").append(referenceTime);
sb.append('}');
return sb.toString();
@@ -127,10 +139,10 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
if (currency != that.currency) {
return false;
}
- if (dateTimeZone != null ? !dateTimeZone.equals(that.dateTimeZone) : that.dateTimeZone != null) {
+ if (timeZone != null ? !timeZone.equals(that.timeZone) : that.timeZone != null) {
return false;
}
- if (fixedOffsetDateTimeZone != null ? !fixedOffsetDateTimeZone.equals(that.fixedOffsetDateTimeZone) : that.fixedOffsetDateTimeZone != null) {
+ if (fixedOffsetTimeZone != null ? !fixedOffsetTimeZone.equals(that.fixedOffsetTimeZone) : that.fixedOffsetTimeZone != null) {
return false;
}
return referenceTime != null ? referenceTime.compareTo(that.referenceTime) == 0 : that.referenceTime == null;
@@ -141,9 +153,19 @@ public class DefaultImmutableAccountData implements ImmutableAccountData {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
result = 31 * result + (currency != null ? currency.hashCode() : 0);
- result = 31 * result + (dateTimeZone != null ? dateTimeZone.hashCode() : 0);
- result = 31 * result + (fixedOffsetDateTimeZone != null ? fixedOffsetDateTimeZone.hashCode() : 0);
+ result = 31 * result + (timeZone != null ? timeZone.hashCode() : 0);
+ result = 31 * result + (fixedOffsetTimeZone != null ? fixedOffsetTimeZone.hashCode() : 0);
result = 31 * result + (referenceTime != null ? referenceTime.hashCode() : 0);
return result;
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/api/src/main/java/org/killbill/billing/entity/EntityBase.java b/api/src/main/java/org/killbill/billing/entity/EntityBase.java
index 0c02112..465debe 100644
--- a/api/src/main/java/org/killbill/billing/entity/EntityBase.java
+++ b/api/src/main/java/org/killbill/billing/entity/EntityBase.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project 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:
*
@@ -19,10 +21,10 @@ package org.killbill.billing.entity;
import java.util.UUID;
import org.joda.time.DateTime;
-
import org.killbill.billing.util.UUIDs;
import org.killbill.billing.util.entity.Entity;
+// Note: not suitable for Serializable Entity classes (e.g. DefaultTenant)
public abstract class EntityBase implements Entity {
protected UUID id;
@@ -45,7 +47,7 @@ public abstract class EntityBase implements Entity {
this.updatedDate = updatedDate;
}
- public EntityBase(final EntityBase target) {
+ public EntityBase(final Entity target) {
this.id = target.getId();
this.createdDate = target.getCreatedDate();
this.updatedDate = target.getUpdatedDate();
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
index 0d1757f..f9aa7e4 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/DefaultPlan.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project 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
@@ -18,6 +18,10 @@
package org.killbill.billing.catalog;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -46,12 +50,17 @@ import org.killbill.billing.catalog.api.PriceList;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.Recurring;
import org.killbill.billing.catalog.api.TimeUnit;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.ValidationError;
import org.killbill.xmlloader.ValidationErrors;
@XmlAccessorType(XmlAccessType.NONE)
-public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements Plan {
+public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements Plan, Externalizable {
+
+ private static final long serialVersionUID = -4159932819592790086L;
@XmlAttribute(required = true)
@XmlID
@@ -80,6 +89,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
private String priceListName;
+ // For deserialization
public DefaultPlan() {
initialPhases = new DefaultPlanPhase[0];
}
@@ -102,31 +112,61 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return effectiveDateForExistingSubscriptions;
}
+ public void setEffectiveDateForExistingSubscriptions(
+ final Date effectiveDateForExistingSubscriptions) {
+ this.effectiveDateForExistingSubscriptions = effectiveDateForExistingSubscriptions;
+ }
+
@Override
public DefaultPlanPhase[] getInitialPhases() {
return initialPhases;
}
+ public DefaultPlan setInitialPhases(final DefaultPlanPhase[] phases) {
+ this.initialPhases = phases;
+ return this;
+ }
+
@Override
public Product getProduct() {
return product;
}
+ public DefaultPlan setProduct(final Product product) {
+ this.product = (DefaultProduct) product;
+ return this;
+ }
+
@Override
public String getPriceListName() {
return priceListName;
}
+ public DefaultPlan setPriceListName(final String priceListName) {
+ this.priceListName = priceListName;
+ return this;
+ }
+
@Override
public String getName() {
return name;
}
+ public DefaultPlan setName(final String name) {
+ this.name = name;
+ return this;
+ }
+
@Override
public DefaultPlanPhase getFinalPhase() {
return finalPhase;
}
+ public DefaultPlan setFinalPhase(final DefaultPlanPhase finalPhase) {
+ this.finalPhase = finalPhase;
+ return this;
+ }
+
@Override
public PlanPhase[] getAllPhases() {
final int length = initialPhases.length + 1;
@@ -162,9 +202,11 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return plansAllowedInBundle;
}
- /* (non-Javadoc)
- * @see org.killbill.billing.catalog.IPlan#getPhaseIterator()
- */
+ public DefaultPlan setPlansAllowedInBundle(final Integer plansAllowedInBundle) {
+ this.plansAllowedInBundle = plansAllowedInBundle;
+ return this;
+ }
+
@Override
public Iterator<PlanPhase> getInitialPhaseIterator() {
final Collection<PlanPhase> list = new ArrayList<PlanPhase>();
@@ -185,7 +227,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
p.setPlan(this);
p.initialize(catalog, sourceURI);
}
- this.priceListName = this.priceListName != null ? this.priceListName : findPriceListForPlan(catalog);
+ this.priceListName = this.priceListName != null ? this.priceListName : findPriceListForPlan(catalog);
}
@Override
@@ -202,7 +244,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
errors.add(new ValidationError(String.format("Invalid product for plan '%s'", name), catalog.getCatalogURI(), DefaultPlan.class, ""));
}
- for (DefaultPlanPhase cur : initialPhases) {
+ for (final DefaultPlanPhase cur : initialPhases) {
cur.validate(catalog, errors);
if (cur.getPhaseType() == PhaseType.EVERGREEN || cur.getPhaseType() == PhaseType.FIXEDTERM) {
errors.add(new ValidationError(String.format("Initial Phase %s of plan %s cannot be of type %s",
@@ -225,41 +267,6 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
return errors;
}
- public void setEffectiveDateForExistingSubscriptions(
- final Date effectiveDateForExistingSubscriptions) {
- this.effectiveDateForExistingSubscriptions = effectiveDateForExistingSubscriptions;
- }
-
- public DefaultPlan setName(final String name) {
- this.name = name;
- return this;
- }
-
- public DefaultPlan setFinalPhase(final DefaultPlanPhase finalPhase) {
- this.finalPhase = finalPhase;
- return this;
- }
-
- public DefaultPlan setProduct(final Product product) {
- this.product = (DefaultProduct) product;
- return this;
- }
-
- public DefaultPlan setPriceListName(final String priceListName) {
- this.priceListName = priceListName;
- return this;
- }
-
- public DefaultPlan setInitialPhases(final DefaultPlanPhase[] phases) {
- this.initialPhases = phases;
- return this;
- }
-
- public DefaultPlan setPlansAllowedInBundle(final Integer plansAllowedInBundle) {
- this.plansAllowedInBundle = plansAllowedInBundle;
- return this;
- }
-
@Override
public DateTime dateOfFirstRecurringNonZeroCharge(final DateTime subscriptionStartDate, final PhaseType initialPhaseType) {
DateTime result = subscriptionStartDate;
@@ -345,4 +352,14 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
}
throw new IllegalStateException("Cannot extract pricelist for plan " + name);
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
index 8d31aff..e52d6a8 100644
--- a/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
+++ b/catalog/src/main/java/org/killbill/billing/catalog/VersionedCatalog.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project 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
@@ -18,6 +18,10 @@
package org.killbill.billing.catalog;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
@@ -58,6 +62,9 @@ import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.Product;
import org.killbill.billing.catalog.api.StaticCatalog;
import org.killbill.billing.catalog.api.Unit;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.clock.Clock;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.ValidationError;
@@ -65,7 +72,9 @@ import org.killbill.xmlloader.ValidationErrors;
@XmlRootElement(name = "catalogs")
@XmlAccessorType(XmlAccessType.NONE)
-public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> implements Catalog, StaticCatalog {
+public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> implements Catalog, StaticCatalog, Externalizable {
+
+ private static final long serialVersionUID = 3181874902672322725L;
private final Clock clock;
@@ -553,4 +562,14 @@ public class VersionedCatalog extends ValidatingConfig<VersionedCatalog> impleme
public boolean compliesWithLimits(final String phaseName, final String unit, final double value) throws CatalogApiException {
return versionForDate(clock.getUTCNow()).compliesWithLimits(phaseName, unit, value);
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
index 9ca684d..0aec675 100644
--- a/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
+++ b/overdue/src/main/java/org/killbill/billing/overdue/config/DefaultOverdueConfig.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project 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:
*
@@ -16,19 +18,29 @@
package org.killbill.billing.overdue.config;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.net.URI;
+
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
-import java.net.URI;
import org.killbill.billing.overdue.api.OverdueConfig;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.ValidationErrors;
@XmlRootElement(name = "overdueConfig")
@XmlAccessorType(XmlAccessType.NONE)
-public class DefaultOverdueConfig extends ValidatingConfig<DefaultOverdueConfig> implements OverdueConfig {
+public class DefaultOverdueConfig extends ValidatingConfig<DefaultOverdueConfig> implements OverdueConfig, Externalizable {
+
+ private static final long serialVersionUID = 1282636602472877120L;
@XmlElement(required = true, name = "accountOverdueStates")
private DefaultOverdueStatesAccount accountOverdueStates = new DefaultOverdueStatesAccount();
@@ -43,14 +55,25 @@ public class DefaultOverdueConfig extends ValidatingConfig<DefaultOverdueConfig>
return accountOverdueStates.validate(root, errors);
}
+ // For deserialization
+ public DefaultOverdueConfig() {}
+
public DefaultOverdueConfig setOverdueStates(final DefaultOverdueStatesAccount accountOverdueStates) {
this.accountOverdueStates = accountOverdueStates;
return this;
}
-
public URI getURI() {
return null;
}
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java b/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
index 2d1c3dc..19194c6 100644
--- a/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
+++ b/payment/src/main/java/org/killbill/billing/payment/caching/EhCacheStateMachineConfigCache.java
@@ -71,7 +71,8 @@ public class EhCacheStateMachineConfigCache implements StateMachineConfigCache {
try {
final InputStream stream = new ByteArrayInputStream(stateMachineConfigXML.getBytes());
- return XMLLoader.getObjectFromStream(new URI("dummy"), stream, DefaultStateMachineConfig.class);
+ final DefaultStateMachineConfig defaultStateMachineConfig = XMLLoader.getObjectFromStream(new URI("dummy"), stream, DefaultStateMachineConfig.class);
+ return new SerializableStateMachineConfig(defaultStateMachineConfig);
} catch (final Exception e) {
// TODO 0.17 proper error code
throw new PaymentApiException(e, ErrorCode.PAYMENT_INTERNAL_ERROR, "Invalid payment state machine config");
diff --git a/payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java b/payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java
new file mode 100644
index 0000000..d4044b6
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/caching/SerializableStateMachineConfig.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.payment.caching;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.killbill.automaton.LinkStateMachine;
+import org.killbill.automaton.MissingEntryException;
+import org.killbill.automaton.StateMachine;
+import org.killbill.automaton.StateMachineConfig;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
+
+public class SerializableStateMachineConfig implements StateMachineConfig, Externalizable {
+
+ private static final long serialVersionUID = 945320595649172168L;
+
+ private StateMachineConfig stateMachineConfig;
+
+ // For deserialization
+ public SerializableStateMachineConfig() {}
+
+ public SerializableStateMachineConfig(final StateMachineConfig stateMachineConfig) {
+ this.stateMachineConfig = stateMachineConfig;
+ }
+
+ @Override
+ public StateMachine[] getStateMachines() {
+ return stateMachineConfig.getStateMachines();
+ }
+
+ @Override
+ public LinkStateMachine[] getLinkStateMachines() {
+ return stateMachineConfig.getLinkStateMachines();
+ }
+
+ @Override
+ public StateMachine getStateMachineForState(final String stateName) throws MissingEntryException {
+ return stateMachineConfig.getStateMachineForState(stateName);
+ }
+
+ @Override
+ public StateMachine getStateMachine(final String stateMachineName) throws MissingEntryException {
+ return stateMachineConfig.getStateMachine(stateMachineName);
+ }
+
+ @Override
+ public LinkStateMachine getLinkStateMachine(final String linkStateMachineName) throws MissingEntryException {
+ return stateMachineConfig.getLinkStateMachine(linkStateMachineName);
+ }
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
index aa2e377..58c87e7 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/KillbillJdbcTenantRealm.java
@@ -1,7 +1,7 @@
/*
* Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2015 Groupon, Inc
- * Copyright 2014-2015 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project 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
@@ -18,6 +18,11 @@
package org.killbill.billing.server.security;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
import javax.sql.DataSource;
import org.apache.shiro.authc.AuthenticationException;
@@ -25,8 +30,12 @@ import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.codec.Base64;
+import org.apache.shiro.codec.Hex;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.util.ByteSource;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.billing.util.config.definition.SecurityConfig;
import org.killbill.billing.util.security.shiro.KillbillCredentialsMatcher;
@@ -61,7 +70,9 @@ public class KillbillJdbcTenantRealm extends JdbcRealm {
// We store the salt bytes in Base64 (because the JdbcRealm retrieves it as a String)
final ByteSource base64Salt = authenticationInfo.getCredentialsSalt();
- authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(Base64.decode(base64Salt.getBytes())));
+ final byte[] bytes = Base64.decode(base64Salt.getBytes());
+ // SimpleByteSource isn't Serializable
+ authenticationInfo.setCredentialsSalt(new VerySimpleByteSource(bytes));
return authenticationInfo;
}
@@ -78,4 +89,56 @@ public class KillbillJdbcTenantRealm extends JdbcRealm {
private void configureDataSource() {
setDataSource(dataSource);
}
+
+ private static final class VerySimpleByteSource implements ByteSource, Externalizable {
+
+ private static final long serialVersionUID = 4498655519894503985L;
+
+ private byte[] bytes;
+ private String cachedHex;
+ private String cachedBase64;
+
+ // For deserialization
+ public VerySimpleByteSource() {}
+
+ VerySimpleByteSource(final byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ @Override
+ public String toHex() {
+ if (this.cachedHex == null) {
+ this.cachedHex = Hex.encodeToString(getBytes());
+ }
+ return this.cachedHex;
+ }
+
+ @Override
+ public String toBase64() {
+ if (this.cachedBase64 == null) {
+ this.cachedBase64 = Base64.encodeToString(getBytes());
+ }
+ return this.cachedBase64;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return this.bytes == null || this.bytes.length == 0;
+ }
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
+ }
}
diff --git a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
index a7f56b6..dbda74f 100644
--- a/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
+++ b/tenant/src/main/java/org/killbill/billing/tenant/api/DefaultTenant.java
@@ -16,22 +16,35 @@
package org.killbill.billing.tenant.api;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.util.UUID;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
-
import org.killbill.billing.tenant.dao.TenantModelDao;
-import org.killbill.billing.entity.EntityBase;
import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
+
+public class DefaultTenant implements Tenant, Externalizable {
-public class DefaultTenant extends EntityBase implements Tenant {
+ private static final long serialVersionUID = -6662488328218280007L;
- private final String externalKey;
- private final String apiKey;
+ private UUID id;
+ private DateTime createdDate;
+ private DateTime updatedDate;
+ private String externalKey;
+ private String apiKey;
// Decrypted (clear) secret
- private final String apiSecret;
+ private transient String apiSecret;
+
+ // For deserialization
+ public DefaultTenant() {}
/**
* This call is used to create a tenant
@@ -54,7 +67,9 @@ public class DefaultTenant extends EntityBase implements Tenant {
public DefaultTenant(final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
final String externalKey, final String apiKey, final String apiSecret) {
- super(id, createdDate, updatedDate);
+ this.id = id;
+ this.createdDate = createdDate;
+ this.updatedDate = updatedDate;
this.externalKey = externalKey;
this.apiKey = apiKey;
this.apiSecret = apiSecret;
@@ -66,6 +81,21 @@ public class DefaultTenant extends EntityBase implements Tenant {
}
@Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ @Override
public String getExternalKey() {
return externalKey;
}
@@ -82,9 +112,11 @@ public class DefaultTenant extends EntityBase implements Tenant {
@Override
public String toString() {
- final StringBuilder sb = new StringBuilder();
- sb.append("DefaultTenant");
- sb.append("{externalKey='").append(externalKey).append('\'');
+ final StringBuilder sb = new StringBuilder("DefaultTenant{");
+ sb.append("id=").append(id);
+ sb.append(", createdDate=").append(createdDate);
+ sb.append(", updatedDate=").append(updatedDate);
+ sb.append(", externalKey='").append(externalKey).append('\'');
sb.append(", apiKey='").append(apiKey).append('\'');
// Don't print the secret
sb.append('}');
@@ -102,24 +134,42 @@ public class DefaultTenant extends EntityBase implements Tenant {
final DefaultTenant that = (DefaultTenant) o;
- if (apiKey != null ? !apiKey.equals(that.apiKey) : that.apiKey != null) {
+ if (id != null ? !id.equals(that.id) : that.id != null) {
+ return false;
+ }
+ if (createdDate != null ? !createdDate.equals(that.createdDate) : that.createdDate != null) {
+ return false;
+ }
+ if (updatedDate != null ? !updatedDate.equals(that.updatedDate) : that.updatedDate != null) {
return false;
}
if (externalKey != null ? !externalKey.equals(that.externalKey) : that.externalKey != null) {
return false;
}
- if (apiSecret != null ? !apiSecret.equals(that.apiSecret) : that.apiSecret != null) {
+ if (apiKey != null ? !apiKey.equals(that.apiKey) : that.apiKey != null) {
return false;
}
-
- return true;
+ return apiSecret != null ? apiSecret.equals(that.apiSecret) : that.apiSecret == null;
}
@Override
public int hashCode() {
- int result = externalKey != null ? externalKey.hashCode() : 0;
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+ result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+ result = 31 * result + (externalKey != null ? externalKey.hashCode() : 0);
result = 31 * result + (apiKey != null ? apiKey.hashCode() : 0);
result = 31 * result + (apiSecret != null ? apiSecret.hashCode() : 0);
return result;
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
util/pom.xml 4(+4 -0)
diff --git a/util/pom.xml b/util/pom.xml
index 1a4974b..3769272 100644
--- a/util/pom.xml
+++ b/util/pom.xml
@@ -50,6 +50,10 @@
<artifactId>jackson-dataformat-csv</artifactId>
</dependency>
<dependency>
+ <groupId>com.fasterxml.jackson.dataformat</groupId>
+ <artifactId>jackson-dataformat-smile</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
diff --git a/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java b/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
index 7ce819b..0c67fb0 100644
--- a/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
+++ b/util/src/main/java/org/killbill/billing/util/audit/dao/AuditLogModelDao.java
@@ -1,7 +1,9 @@
/*
* Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project 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:
*
@@ -16,26 +18,98 @@
package org.killbill.billing.util.audit.dao;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
import org.killbill.billing.util.audit.AuditLog;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
import org.killbill.billing.util.callcontext.CallContext;
import org.killbill.billing.util.dao.EntityAudit;
import org.killbill.billing.util.dao.TableName;
import org.killbill.billing.util.entity.dao.EntityModelDao;
-public class AuditLogModelDao extends EntityAudit implements EntityModelDao<AuditLog> {
+public class AuditLogModelDao implements EntityModelDao<AuditLog>, Externalizable {
+
+ private UUID id;
+ private DateTime createdDate;
+ private DateTime updatedDate;
+ private TableName tableName;
+ private Long targetRecordId;
+ private ChangeType changeType;
+ private CallContext callContext;
- private final CallContext callContext;
+ private Long recordId;
+ private Long accountRecordId;
+ private Long tenantRecordId;
+
+ // For deserialization
+ public AuditLogModelDao() {}
public AuditLogModelDao(final EntityAudit entityAudit, final CallContext callContext) {
- super(entityAudit.getId(), entityAudit.getTableName(), entityAudit.getTargetRecordId(), entityAudit.getChangeType(), entityAudit.getCreatedDate());
+ this.id = entityAudit.getId();
+ this.tableName = entityAudit.getTableName();
+ this.targetRecordId = entityAudit.getTargetRecordId();
+ this.changeType = entityAudit.getChangeType();
+ this.createdDate = entityAudit.getCreatedDate();
+ this.updatedDate = null;
this.callContext = callContext;
}
+ @Override
+ public UUID getId() {
+ return id;
+ }
+
+ @Override
+ public DateTime getCreatedDate() {
+ return createdDate;
+ }
+
+ @Override
+ public DateTime getUpdatedDate() {
+ return updatedDate;
+ }
+
+ @Override
+ public TableName getTableName() {
+ return tableName;
+ }
+
+ public Long getTargetRecordId() {
+ return targetRecordId;
+ }
+
+ public ChangeType getChangeType() {
+ return changeType;
+ }
+
public CallContext getCallContext() {
return callContext;
}
@Override
+ public Long getRecordId() {
+ return recordId;
+ }
+
+ @Override
+ public Long getAccountRecordId() {
+ return accountRecordId;
+ }
+
+ @Override
+ public Long getTenantRecordId() {
+ return tenantRecordId;
+ }
+
+ @Override
public TableName getHistoryTableName() {
return null;
}
@@ -43,7 +117,16 @@ public class AuditLogModelDao extends EntityAudit implements EntityModelDao<Audi
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("AuditLogModelDao{");
- sb.append("callContext=").append(callContext);
+ sb.append("id=").append(id);
+ sb.append(", createdDate=").append(createdDate);
+ sb.append(", updatedDate=").append(updatedDate);
+ sb.append(", tableName=").append(tableName);
+ sb.append(", targetRecordId=").append(targetRecordId);
+ sb.append(", changeType=").append(changeType);
+ sb.append(", callContext=").append(callContext);
+ sb.append(", recordId=").append(recordId);
+ sb.append(", accountRecordId=").append(accountRecordId);
+ sb.append(", tenantRecordId=").append(tenantRecordId);
sb.append('}');
return sb.toString();
}
@@ -56,23 +139,61 @@ public class AuditLogModelDao extends EntityAudit implements EntityModelDao<Audi
if (o == null || getClass() != o.getClass()) {
return false;
}
- if (!super.equals(o)) {
- return false;
- }
final AuditLogModelDao that = (AuditLogModelDao) o;
+ if (id != null ? !id.equals(that.id) : that.id != null) {
+ return false;
+ }
+ if (createdDate != null ? createdDate.compareTo(that.createdDate) != 0 : that.createdDate != null) {
+ return false;
+ }
+ if (updatedDate != null ? updatedDate.compareTo(that.updatedDate) != 0 : that.updatedDate != null) {
+ return false;
+ }
+ if (tableName != that.tableName) {
+ return false;
+ }
+ if (targetRecordId != null ? !targetRecordId.equals(that.targetRecordId) : that.targetRecordId != null) {
+ return false;
+ }
+ if (changeType != that.changeType) {
+ return false;
+ }
if (callContext != null ? !callContext.equals(that.callContext) : that.callContext != null) {
return false;
}
-
- return true;
+ if (recordId != null ? !recordId.equals(that.recordId) : that.recordId != null) {
+ return false;
+ }
+ if (accountRecordId != null ? !accountRecordId.equals(that.accountRecordId) : that.accountRecordId != null) {
+ return false;
+ }
+ return tenantRecordId != null ? tenantRecordId.equals(that.tenantRecordId) : that.tenantRecordId == null;
}
@Override
public int hashCode() {
- int result = super.hashCode();
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (createdDate != null ? createdDate.hashCode() : 0);
+ result = 31 * result + (updatedDate != null ? updatedDate.hashCode() : 0);
+ result = 31 * result + (tableName != null ? tableName.hashCode() : 0);
+ result = 31 * result + (targetRecordId != null ? targetRecordId.hashCode() : 0);
+ result = 31 * result + (changeType != null ? changeType.hashCode() : 0);
result = 31 * result + (callContext != null ? callContext.hashCode() : 0);
+ result = 31 * result + (recordId != null ? recordId.hashCode() : 0);
+ result = 31 * result + (accountRecordId != null ? accountRecordId.hashCode() : 0);
+ result = 31 * result + (tenantRecordId != null ? tenantRecordId.hashCode() : 0);
return result;
}
+
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
index eb90621..97d5cb6 100644
--- a/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
+++ b/util/src/main/java/org/killbill/billing/util/cache/Cachable.java
@@ -53,6 +53,7 @@ public @interface Cachable {
CacheType value();
+ // Make sure both the key and value are Serializable
enum CacheType {
/* Mapping from object 'id (UUID as String)' -> object 'recordId (Long)' */
@@ -67,19 +68,19 @@ public @interface Cachable {
/* Mapping from object 'recordId (Long as String)' -> object 'id (UUID)' */
OBJECT_ID(OBJECT_ID_CACHE_NAME, String.class, UUID.class, true),
- /* Mapping from object 'tableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
+ /* Mapping from object 'tableName::targetRecordId' -> matching objects 'List<AuditLogModelDao>' */
AUDIT_LOG(AUDIT_LOG_CACHE_NAME, String.class, List.class, true),
- /* Mapping from object 'tableName::historyTableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
+ /* Mapping from object 'tableName::historyTableName::targetRecordId' -> matching objects 'List<AuditLogModelDao>' */
AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME, String.class, List.class, true),
/* Tenant catalog cache */
TENANT_CATALOG(TENANT_CATALOG_CACHE_NAME, Long.class, Catalog.class, false),
- /* Tenant payment state machine config cache */
+ /* Tenant payment state machine config cache (String -> SerializableStateMachineConfig) */
TENANT_PAYMENT_STATE_MACHINE_CONFIG(TENANT_PAYMENT_STATE_MACHINE_CONFIG_CACHE_NAME, String.class, Object.class, false),
- /* Tenant overdue config cache */
+ /* Tenant overdue config cache (String -> DefaultOverdueConfig) */
TENANT_OVERDUE_CONFIG(TENANT_OVERDUE_CONFIG_CACHE_NAME, Long.class, Object.class, false),
/* Tenant overdue config cache */
@@ -88,7 +89,7 @@ public @interface Cachable {
/* Tenant config cache */
TENANT_KV(TENANT_KV_CACHE_NAME, String.class, String.class, false),
- /* Tenant config cache */
+ /* Tenant cache */
TENANT(TENANT_CACHE_NAME, String.class, Tenant.class, false),
/* Overwritten plans */
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ExternalizableInput.java b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableInput.java
new file mode 100644
index 0000000..73e45a7
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableInput.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 2017 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.cache;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+
+// See http://www.cowtowncoder.com/blog/archives/2012/08/entry_477.html
+public class ExternalizableInput extends InputStream {
+
+ private final ObjectInput in;
+
+ public ExternalizableInput(final ObjectInput in) {
+ this.in = in;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return in.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ in.close();
+ }
+
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return in.read();
+ }
+
+ @Override
+ public int read(final byte[] buffer) throws IOException {
+ return in.read(buffer);
+ }
+
+ @Override
+ public int read(final byte[] buffer, final int offset, final int len) throws IOException {
+ return in.read(buffer, offset, len);
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ return in.skip(n);
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/ExternalizableOutput.java b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableOutput.java
new file mode 100644
index 0000000..e5297d4
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/ExternalizableOutput.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 2017 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.cache;
+
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.io.OutputStream;
+
+// See http://www.cowtowncoder.com/blog/archives/2012/08/entry_477.html
+public class ExternalizableOutput extends OutputStream {
+
+ private final ObjectOutput out;
+
+ public ExternalizableOutput(final ObjectOutput out) {
+ this.out = out;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ out.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ out.close();
+ }
+
+ @Override
+ public void write(final int ch) throws IOException {
+ out.write(ch);
+ }
+
+ @Override
+ public void write(final byte[] data) throws IOException {
+ out.write(data);
+ }
+
+ @Override
+ public void write(final byte[] data, final int offset, final int len) throws IOException {
+ out.write(data, offset, len);
+ }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/cache/MapperHolder.java b/util/src/main/java/org/killbill/billing/util/cache/MapperHolder.java
new file mode 100644
index 0000000..65a8f8a
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/cache/MapperHolder.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 Groupon, Inc
+ * Copyright 2017 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.util.cache;
+
+import org.killbill.billing.util.jackson.ObjectMapper;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.dataformat.smile.SmileFactory;
+
+import static com.fasterxml.jackson.core.JsonGenerator.Feature.AUTO_CLOSE_TARGET;
+
+// See http://www.cowtowncoder.com/blog/archives/2012/08/entry_477.html
+public class MapperHolder {
+
+ private static final MapperHolder instance = new MapperHolder();
+
+ static {
+ instance.mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
+ instance.mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
+ // Stream is NOT owned by Jackson
+ instance.mapper.disable(AUTO_CLOSE_TARGET);
+ }
+
+ private final SmileFactory f = new SmileFactory();
+ private final ObjectMapper mapper = new ObjectMapper(f);
+
+ public static ObjectMapper mapper() { return instance.mapper; }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
index 0d04b4a..25a3f34 100644
--- a/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
+++ b/util/src/main/java/org/killbill/billing/util/config/tenant/PerTenantConfig.java
@@ -1,6 +1,6 @@
/*
- * Copyright 2014-2016 Groupon, Inc
- * Copyright 2014-2016 The Billing Project, LLC
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
* The Billing Project 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
@@ -17,11 +17,30 @@
package org.killbill.billing.util.config.tenant;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
import java.util.HashMap;
-public class PerTenantConfig extends HashMap<String, String> {
+import org.killbill.billing.util.cache.ExternalizableInput;
+import org.killbill.billing.util.cache.ExternalizableOutput;
+import org.killbill.billing.util.cache.MapperHolder;
+
+public class PerTenantConfig extends HashMap<String, String> implements Externalizable {
+
+ private static final long serialVersionUID = 3887971108446630172L;
public PerTenantConfig() {
}
+ @Override
+ public void readExternal(final ObjectInput in) throws IOException {
+ MapperHolder.mapper().readerForUpdating(this).readValue(new ExternalizableInput(in));
+ }
+
+ @Override
+ public void writeExternal(final ObjectOutput oo) throws IOException {
+ MapperHolder.mapper().writeValue(new ExternalizableOutput(oo), this);
+ }
}
diff --git a/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java b/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java
index 2e69423..4ff3026 100644
--- a/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java
+++ b/util/src/main/java/org/killbill/billing/util/jackson/ObjectMapper.java
@@ -1,7 +1,9 @@
/*
- * Copyright 2010-2011 Ning, Inc.
+ * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2017 Groupon, Inc
+ * Copyright 2014-2017 The Billing Project, LLC
*
- * Ning licenses this file to you under the Apache License, version 2.0
+ * The Billing Project 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:
*
@@ -17,11 +19,18 @@
package org.killbill.billing.util.jackson;
import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.dataformat.smile.SmileFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;
public class ObjectMapper extends com.fasterxml.jackson.databind.ObjectMapper {
- public ObjectMapper() {
+
+ public ObjectMapper(final SmileFactory f) {
+ super(f);
this.registerModule(new JodaModule());
this.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
+
+ public ObjectMapper() {
+ this(null);
+ }
}