Skip to content

Commit

Permalink
catalog: Add introspection initialization for non required null field…
Browse files Browse the repository at this point in the history
… of type Integer, Enum, ... (in accordance with our existing default). See #678

* SafetyInitializer : Add new cases
* Fix existing broken catalogs (from previous commit 26e650f) due to stricter catalog validation
  • Loading branch information
sbrossie committed Dec 20, 2016
1 parent 26e650f commit 67097f0
Show file tree
Hide file tree
Showing 39 changed files with 196 additions and 48 deletions.
Expand Up @@ -23,14 +23,18 @@
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;

import org.killbill.billing.catalog.api.BlockType;
import org.killbill.billing.catalog.api.FixedType;

public class CatalogSafetyInitializer {

public static final Integer DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE = -1;

//
// Ensure that all uninitialized arrays for which there is neither a 'required' XmlElementWrapper or XmlElement annotation
// end up initialized with a default zero length array (allowing to safely get the length and iterate over (0) element.
//
public static void initializeNonRequiredArrayFields(final Object obj) {
public static void initializeNonRequiredNullFieldsWithDefaultValue(final Object obj) {
try {
final Field[] fields = obj.getClass().getDeclaredFields();
for (final Field f : fields) {
Expand All @@ -46,6 +50,16 @@ public static void initializeNonRequiredArrayFields(final Object obj) {
initializeArrayIfNull(obj, f);
}
}
} else if (!f.getType().isPrimitive()) {
if (f.getType().isEnum()) {
if (FixedType.class.equals(f.getType())) {
initializeFieldWithValue(obj, f, FixedType.ONE_TIME);
} else if (BlockType.class.equals(f.getType())) {
initializeFieldWithValue(obj, f, BlockType.VANILLA);
}
} else if (Integer.class.equals(f.getType())) {
initializeFieldWithValue(obj, f, DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE);
}
}
}
} catch (final IllegalAccessException e) {
Expand All @@ -55,6 +69,14 @@ public static void initializeNonRequiredArrayFields(final Object obj) {
}
}

private static void initializeFieldWithValue(final Object obj, final Field f, final Object value) throws IllegalAccessException, ClassNotFoundException {
f.setAccessible(true);
if (f.get(obj) == null) {
f.set(obj, value);
}
f.setAccessible(false);
}

private static void initializeArrayIfNull(final Object obj, final Field f) throws IllegalAccessException, ClassNotFoundException {
f.setAccessible(true);
if (f.get(obj) == null) {
Expand All @@ -63,6 +85,7 @@ private static void initializeArrayIfNull(final Object obj, final Field f) throw
f.setAccessible(false);
}


private static Object[] getZeroLengthArrayInitializer(final Field f) throws ClassNotFoundException {
// Yack... type erasure, why?
final String arrayClassName = f.getType().getCanonicalName();
Expand Down
Expand Up @@ -86,6 +86,11 @@ public Double getMinTopUpCredit() throws CatalogApiException {

@Override
public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {
// Safety check
if (type == null) {
throw new IllegalStateException("type should have been automatically been initialized with VANILLA ");
}

if (type == BlockType.TOP_UP && minTopUpCredit == null) {
errors.add(new ValidationError(String.format("TOP_UP block needs to define minTopUpCredit for phase %s",
phase.getName()), catalog.getCatalogURI(), DefaultUsage.class, ""));
Expand Down
Expand Up @@ -16,6 +16,8 @@

package org.killbill.billing.catalog;

import java.net.URI;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
Expand All @@ -35,7 +37,6 @@
@XmlAccessorType(XmlAccessType.NONE)
public class DefaultDuration extends ValidatingConfig<StandaloneCatalog> implements Duration {

public static final int DEFAULT_DURATION_NUMBER = -1;
@XmlElement(required = true)
private TimeUnit unit;

Expand All @@ -59,7 +60,6 @@ public int getNumber() {
}

public DefaultDuration() {
number = DEFAULT_DURATION_NUMBER;
}

@Override
Expand Down Expand Up @@ -102,17 +102,30 @@ public LocalDate addToLocalDate(final LocalDate localDate) throws CatalogApiExce

@Override
public ValidationErrors validate(final StandaloneCatalog catalog, final ValidationErrors errors) {

// Safety check
if (number == null) {
throw new IllegalStateException("number should have been automatically been initialized with DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE ");
}

//Validation: TimeUnit UNLIMITED if number == -1
if ((unit == TimeUnit.UNLIMITED && number != DEFAULT_DURATION_NUMBER)) {
if ((unit == TimeUnit.UNLIMITED && number != CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE)) {
errors.add(new ValidationError("Duration can only have 'UNLIMITED' unit if the number is omitted",
catalog.getCatalogURI(), DefaultDuration.class, ""));
} else if ((unit != TimeUnit.UNLIMITED) && number == DEFAULT_DURATION_NUMBER) {
} else if ((unit != TimeUnit.UNLIMITED) && number == CatalogSafetyInitializer.DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE) {
errors.add(new ValidationError("Finite Duration must have a well defined length",
catalog.getCatalogURI(), DefaultDuration.class, ""));
}
return errors;
}

@Override
public void initialize(final StandaloneCatalog root, final URI uri) {
super.initialize(root, uri);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}


public DefaultDuration setUnit(final TimeUnit unit) {
this.unit = unit;
return this;
Expand Down
Expand Up @@ -16,15 +16,13 @@

package org.killbill.billing.catalog;

import java.math.BigDecimal;
import java.net.URI;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;

import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.Fixed;
import org.killbill.billing.catalog.api.FixedType;
import org.killbill.billing.catalog.api.InternationalPrice;
Expand Down Expand Up @@ -52,7 +50,6 @@ public InternationalPrice getPrice() {
}

public DefaultFixed() {
type = FixedType.ONE_TIME;
}

public DefaultFixed(final DefaultFixed in, final PlanPhasePriceOverride override) {
Expand All @@ -63,15 +60,18 @@ public DefaultFixed(final DefaultFixed in, final PlanPhasePriceOverride override
@Override
public void initialize(final StandaloneCatalog root, final URI uri) {
super.initialize(root, uri);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);

CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
if (fixedPrice != null) {
fixedPrice.initialize(root, uri);
}
}

@Override
public ValidationErrors validate(final StandaloneCatalog root, final ValidationErrors errors) {
// Safety check
if (type == null) {
throw new IllegalStateException("fixedPrice should have been automatically been initialized with ONE_TIME ");
}
return errors;
}

Expand Down
Expand Up @@ -125,7 +125,7 @@ private boolean currencyIsSupported(final Currency currency, final Currency[] su
@Override
public void initialize(final StandaloneCatalog root, final URI uri) {
super.initialize(root, uri);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}


Expand Down
Expand Up @@ -81,7 +81,7 @@ public ValidationErrors validate(StandaloneCatalog root, ValidationErrors errors
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}


Expand Down
Expand Up @@ -75,7 +75,7 @@ public class DefaultPlan extends ValidatingConfig<StandaloneCatalog> implements
//No other value is allowed for Tiered ADDONS
//A value of -1 means unlimited
@XmlElement(required = false)
private Integer plansAllowedInBundle = -1;
private Integer plansAllowedInBundle;

private String priceListName;

Expand Down Expand Up @@ -174,7 +174,7 @@ public Iterator<PlanPhase> getInitialPhaseIterator() {
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);

if (finalPhase != null) {
finalPhase.setPlan(this);
Expand Down Expand Up @@ -217,6 +217,10 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
finalPhase.getName(), name, finalPhase.getPhaseType()),
catalog.getCatalogURI(), DefaultPlan.class, ""));
}
// Safety check
if (plansAllowedInBundle == null) {
throw new IllegalStateException("plansAllowedInBundle should have been automatically been initialized with DEFAULT_NON_REQUIRED_INTEGER_FIELD_VALUE (-1)");
}
return errors;
}

Expand Down
Expand Up @@ -163,7 +163,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
public void initialize(final StandaloneCatalog root, final URI uri) {

super.initialize(root, uri);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);

if (fixed != null) {
fixed.initialize(root, uri);
Expand All @@ -177,6 +177,7 @@ public void initialize(final StandaloneCatalog root, final URI uri) {
usage.initialize(root, uri);
usage.setPhase(this);
}
duration.initialize(root, uri);
}

public DefaultPlanPhase setFixed(final DefaultFixed fixed) {
Expand Down
Expand Up @@ -83,7 +83,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}


Expand Down
Expand Up @@ -102,7 +102,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}

public DefaultPriceList setName(final String name) {
Expand Down
Expand Up @@ -109,7 +109,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}


Expand Down
Expand Up @@ -152,7 +152,7 @@ public boolean compliesWithLimits(String unit, double value) {
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
for (DefaultLimit cur : limits) {
cur.initialize(catalog, sourceURI);
}
Expand Down
Expand Up @@ -65,7 +65,7 @@ public DefaultInternationalPrice getRecurringPrice() {
@Override
public void initialize(final StandaloneCatalog root, final URI uri) {
super.initialize(root, uri);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
if (recurringPrice != null) {
recurringPrice.initialize(root, uri);
}
Expand Down
Expand Up @@ -133,7 +133,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}


Expand Down
Expand Up @@ -51,7 +51,7 @@ public ValidationErrors validate(StandaloneCatalog root, ValidationErrors errors
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}


Expand Down
Expand Up @@ -165,7 +165,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
@Override
public void initialize(final StandaloneCatalog root, final URI uri) {
super.initialize(root, uri);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);

for (DefaultLimit limit : limits) {
limit.initialize(root, uri);
Expand Down
Expand Up @@ -49,7 +49,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
@Override
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}

@Override
Expand Down
Expand Up @@ -300,7 +300,7 @@ public ValidationErrors validate(final StandaloneCatalog catalog, final Validati
public void initialize(final StandaloneCatalog catalog, final URI sourceURI) {

super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);

catalogURI = sourceURI;
planRules.initialize(catalog, sourceURI);
Expand Down
Expand Up @@ -79,7 +79,7 @@ public Plan createOrFindCurrentPlan(final PlanSpecifier spec, final PlanPhasePri
}

final InternalCallContext internalCallContext = overrides.getCallContext() != null ? internalCallContextFactory.createInternalCallContextWithoutAccountRecordId(overrides.getCallContext()) : null;
return priceOverride.getOrCreateOverriddenPlan(defaultPlan, CatalogDateHelper.toUTCDateTime(getEffectiveDate()), overrides.getOverrides(), internalCallContext);
return priceOverride.getOrCreateOverriddenPlan(this, defaultPlan, CatalogDateHelper.toUTCDateTime(getEffectiveDate()), overrides.getOverrides(), internalCallContext);
}

@Override
Expand Down
Expand Up @@ -413,7 +413,7 @@ public void initialize(final VersionedCatalog catalog, final URI sourceURI) {
// *without** recursively through each StandaloneCatalog
//
super.initialize(catalog, sourceURI);
CatalogSafetyInitializer.initializeNonRequiredArrayFields(this);
CatalogSafetyInitializer.initializeNonRequiredNullFieldsWithDefaultValue(this);
}

@Override
Expand Down
Expand Up @@ -27,6 +27,7 @@
import org.killbill.billing.callcontext.InternalTenantContext;
import org.killbill.billing.catalog.DefaultPlan;
import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
import org.killbill.billing.catalog.StandaloneCatalog;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.catalog.api.Plan;
Expand Down Expand Up @@ -93,7 +94,9 @@ private DefaultPlan loadOverriddenPlan(final String planName, final StaticCatalo
final DefaultPlan defaultPlan = (DefaultPlan) catalog.findCurrentPlan(parentPlanName);

final PlanPhasePriceOverride[] overrides = createOverrides(defaultPlan, phaseDefs);
return new DefaultPlan(planName, defaultPlan, overrides);
final DefaultPlan result = new DefaultPlan(planName, defaultPlan, overrides);
result.initialize((StandaloneCatalog) catalog, ((StandaloneCatalog) catalog).getCatalogURI());
return result;
}

private PlanPhasePriceOverride[] createOverrides(final Plan defaultPlan, final List<CatalogOverridePhaseDefinitionModelDao> phaseDefs) {
Expand Down
Expand Up @@ -30,6 +30,8 @@
import org.killbill.billing.catalog.DefaultPlan;
import org.killbill.billing.catalog.DefaultPlanPhase;
import org.killbill.billing.catalog.DefaultPlanPhasePriceOverride;
import org.killbill.billing.catalog.StandaloneCatalog;
import org.killbill.billing.catalog.StandaloneCatalogWithPriceOverride;
import org.killbill.billing.catalog.api.CatalogApiException;
import org.killbill.billing.catalog.api.Plan;
import org.killbill.billing.catalog.api.PlanPhase;
Expand Down Expand Up @@ -60,7 +62,7 @@ public DefaultPriceOverride(final CatalogOverrideDao overrideDao, final Overridd
}

@Override
public DefaultPlan getOrCreateOverriddenPlan(final Plan parentPlan, final DateTime catalogEffectiveDate, final List<PlanPhasePriceOverride> overrides, @Nullable final InternalCallContext context) throws CatalogApiException {
public DefaultPlan getOrCreateOverriddenPlan(final StandaloneCatalog standaloneCatalog, final Plan parentPlan, final DateTime catalogEffectiveDate, final List<PlanPhasePriceOverride> overrides, @Nullable final InternalCallContext context) throws CatalogApiException {

final PlanPhasePriceOverride[] resolvedOverride = new PlanPhasePriceOverride[parentPlan.getAllPhases().length];
int index = 0;
Expand Down Expand Up @@ -110,6 +112,7 @@ public boolean apply(final PlanPhasePriceOverride input) {
planName = new StringBuffer(parentPlan.getName()).append("-dryrun-").append(DRY_RUN_PLAN_IDX.incrementAndGet()).toString();
}
final DefaultPlan result = new DefaultPlan(planName, (DefaultPlan) parentPlan, resolvedOverride);
result.initialize(standaloneCatalog, standaloneCatalog.getCatalogURI());
if (context == null) {
overriddenPlanCache.addDryRunPlan(planName, result);
}
Expand Down

1 comment on commit 67097f0

@pierre
Copy link
Member

@pierre pierre commented on 67097f0 Dec 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.