killbill-memoizeit
Changes
payment/pom.xml 14(+13 -1)
payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java 7(+0 -7)
payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java 170(+76 -94)
payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java 47(+22 -25)
Details
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java b/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
index 323394c..2097a57 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/util/RefundChecker.java
@@ -37,7 +37,6 @@ import org.killbill.billing.payment.api.DirectPaymentTransaction;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
import org.killbill.billing.util.callcontext.CallContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -68,7 +67,6 @@ public class RefundChecker {
public DirectPaymentTransaction checkRefund(final UUID paymentId, final CallContext context, ExpectedRefundCheck expected) throws PaymentApiException {
-
final DirectPayment payment = paymentApi.getPayment(paymentId, false, ImmutableList.<PluginProperty>of(), context);
final DirectPaymentTransaction refund = Iterables.tryFind(payment.getTransactions(), new Predicate<DirectPaymentTransaction>() {
@Override
payment/pom.xml 14(+13 -1)
diff --git a/payment/pom.xml b/payment/pom.xml
index 343a324..a84a534 100644
--- a/payment/pom.xml
+++ b/payment/pom.xml
@@ -27,6 +27,15 @@
<name>killbill-payment</name>
<dependencies>
<dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ <version>${jackson.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
@@ -52,6 +61,10 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.ning</groupId>
+ <artifactId>compress-lzf</artifactId>
+ </dependency>
+ <dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
@@ -168,7 +181,6 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
-
<!-- TEST SCOPE -->
<dependency>
<groupId>org.slf4j</groupId>
diff --git a/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java b/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
index 2fd7dd6..b9920c0 100644
--- a/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/control/dao/InvoicePaymentControlDao.java
@@ -29,17 +29,10 @@ import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.killbill.billing.catalog.api.Currency;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.PreparedBatch;
-import org.skife.jdbi.v2.PreparedBatchPart;
-import org.skife.jdbi.v2.TransactionCallback;
-import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.tweak.HandleCallback;
-import com.google.common.base.Objects;
-
public class InvoicePaymentControlDao {
private final IDBI dbi;
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
index 7c58873..c733015 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/PluginControlledPaymentProcessor.java
@@ -17,14 +17,12 @@
package org.killbill.billing.payment.core;
import java.math.BigDecimal;
-import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import javax.annotation.Nullable;
import javax.inject.Inject;
-import org.joda.time.DateTime;
import org.killbill.automaton.State;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
@@ -37,14 +35,13 @@ import org.killbill.billing.osgi.api.OSGIServiceRegistration;
import org.killbill.billing.payment.api.DirectPayment;
import org.killbill.billing.payment.api.PaymentApiException;
import org.killbill.billing.payment.api.PluginProperty;
-import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.core.sm.PluginControlledDirectPaymentAutomatonRunner;
-import org.killbill.billing.payment.dao.PaymentModelDao;
-import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentDao;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
+import org.killbill.billing.payment.dao.PaymentModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
import org.killbill.billing.tag.TagInternalApi;
import org.killbill.billing.util.callcontext.CallContext;
@@ -53,10 +50,7 @@ import org.killbill.bus.api.PersistentBus;
import org.killbill.clock.Clock;
import org.killbill.commons.locker.GlobalLocker;
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import com.google.inject.name.Named;
import static org.killbill.billing.payment.glue.PaymentModule.PLUGIN_EXECUTOR_NAMED;
@@ -85,17 +79,17 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
public DirectPayment createAuthorization(final boolean isApiPayment, final Account account, final UUID paymentMethodId, @Nullable final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey, final String transactionExternalKey,
final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
- TransactionType.AUTHORIZE,
- account,
- paymentMethodId,
- directPaymentId,
- paymentExternalKey,
- transactionExternalKey,
- amount,
- currency,
- properties,
- paymentControlPluginName,
- callContext, internalCallContext);
+ TransactionType.AUTHORIZE,
+ account,
+ paymentMethodId,
+ directPaymentId,
+ paymentExternalKey,
+ transactionExternalKey,
+ amount,
+ currency,
+ properties,
+ paymentControlPluginName,
+ callContext, internalCallContext);
}
public DirectPayment createCapture(final boolean isApiPayment, final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency,
@@ -103,66 +97,66 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
final Iterable<PluginProperty> properties, final String paymentControlPluginName,
final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
- TransactionType.CAPTURE,
- account,
- null,
- directPaymentId,
- null,
- transactionExternalKey,
- amount,
- currency,
- properties,
- paymentControlPluginName,
- callContext, internalCallContext);
+ TransactionType.CAPTURE,
+ account,
+ null,
+ directPaymentId,
+ null,
+ transactionExternalKey,
+ amount,
+ currency,
+ properties,
+ paymentControlPluginName,
+ callContext, internalCallContext);
}
public DirectPayment createPurchase(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID directPaymentId, final BigDecimal amount, final Currency currency,
final String paymentExternalKey, final String transactionExternalKey, final Iterable<PluginProperty> properties,
final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
- TransactionType.PURCHASE,
- account,
- paymentMethodId,
- directPaymentId,
- paymentExternalKey,
- transactionExternalKey,
- amount,
- currency,
- properties,
- paymentControlPluginName,
- callContext, internalCallContext);
+ TransactionType.PURCHASE,
+ account,
+ paymentMethodId,
+ directPaymentId,
+ paymentExternalKey,
+ transactionExternalKey,
+ amount,
+ currency,
+ properties,
+ paymentControlPluginName,
+ callContext, internalCallContext);
}
public DirectPayment createVoid(final boolean isApiPayment, final Account account, final UUID directPaymentId, final String transactionExternalKey,
final Iterable<PluginProperty> properties, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
- TransactionType.VOID,
- account,
- null,
- directPaymentId,
- null,
- transactionExternalKey,
- null,
- null,
- properties,
- null,
- callContext, internalCallContext);
+ TransactionType.VOID,
+ account,
+ null,
+ directPaymentId,
+ null,
+ transactionExternalKey,
+ null,
+ null,
+ properties,
+ null,
+ callContext, internalCallContext);
}
public DirectPayment createRefund(final boolean isApiPayment, final Account account, final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String transactionExternalKey,
final Iterable<PluginProperty> properties, final String paymentControlPluginName, final CallContext callContext, final InternalCallContext internalCallContext) throws PaymentApiException {
return pluginControlledDirectPaymentAutomatonRunner.run(isApiPayment,
- TransactionType.REFUND,
- account,
- null,
- directPaymentId,
- null,
- transactionExternalKey,
- amount,
- currency,
- properties,
- paymentControlPluginName,
- callContext, internalCallContext);
+ TransactionType.REFUND,
+ account,
+ null,
+ directPaymentId,
+ null,
+ transactionExternalKey,
+ amount,
+ currency,
+ properties,
+ paymentControlPluginName,
+ callContext, internalCallContext);
}
public DirectPayment createCredit(final boolean isApiPayment, final Account account, final UUID paymentMethodId, final UUID directPaymentId, final BigDecimal amount, final Currency currency, final String paymentExternalKey,
@@ -198,7 +192,6 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
callContext, internalCallContext);
}
-
public void retryPaymentTransaction(final UUID attemptId, final String pluginName, final InternalCallContext internalCallContext) {
try {
@@ -206,44 +199,33 @@ public class PluginControlledPaymentProcessor extends ProcessorBase {
final PaymentModelDao payment = paymentDao.getDirectPaymentByExternalKey(attempt.getPaymentExternalKey(), internalCallContext);
final UUID paymentId = payment != null ? payment.getId() : null;
- final List<PluginPropertyModelDao> properties = paymentDao.getProperties(attempt.getId(), internalCallContext);
- final List<PluginProperty> pluginProperties = properties == null ?
- ImmutableList.<PluginProperty>of() :
- ImmutableList.<PluginProperty>copyOf(Iterables.transform(properties, new Function<PluginPropertyModelDao, PluginProperty>() {
- @Nullable
- @Override
- public PluginProperty apply(final PluginPropertyModelDao input) {
- return new PluginProperty(input.getPropKey(), input.getPropValue(), false);
- }
- }));
-
+ final Iterable<PluginProperty> pluginProperties = PluginPropertySerializer.deserialize(attempt.getPluginProperties());
final Account account = accountInternalApi.getAccountById(attempt.getAccountId(), internalCallContext);
final UUID tenantId = nonEntityDao.retrieveIdFromObject(internalCallContext.getTenantRecordId(), ObjectType.TENANT);
final CallContext callContext = internalCallContext.toCallContext(tenantId);
-
final State state = pluginControlledDirectPaymentAutomatonRunner.fetchState(attempt.getStateName());
pluginControlledDirectPaymentAutomatonRunner.run(state,
- false,
- attempt.getTransactionType(),
- account,
- attempt.getPaymentMethodId(),
- paymentId,
- attempt.getPaymentExternalKey(),
- attempt.getTransactionExternalKey(),
- attempt.getAmount(),
- attempt.getCurrency(),
- pluginProperties,
- pluginName,
- callContext,
- internalCallContext);
+ false,
+ attempt.getTransactionType(),
+ account,
+ attempt.getPaymentMethodId(),
+ paymentId,
+ attempt.getPaymentExternalKey(),
+ attempt.getTransactionExternalKey(),
+ attempt.getAmount(),
+ attempt.getCurrency(),
+ pluginProperties,
+ pluginName,
+ callContext,
+ internalCallContext);
} catch (AccountApiException e) {
- e.printStackTrace();
+ log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
} catch (PaymentApiException e) {
- e.printStackTrace();
+ log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
+ } catch (PluginPropertySerializerException e) {
+ log.warn("Failed to retry attempt " + attemptId + " for plugin " + pluginName, e);
}
-
}
-
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
index 47a8ba0..23d619d 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/ProcessorBase.java
@@ -80,7 +80,7 @@ public abstract class ProcessorBase {
protected final TagInternalApi tagInternalApi;
protected final Clock clock;
- private static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
+ protected static final Logger log = LoggerFactory.getLogger(ProcessorBase.class);
protected final InvoiceInternalApi invoiceApi;
public ProcessorBase(final OSGIServiceRegistration<PaymentPluginApi> pluginRegistry,
diff --git a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
index 2c1d56b..cb7e6c2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
+++ b/payment/src/main/java/org/killbill/billing/payment/core/sm/RetryLeavingStateCallback.java
@@ -16,22 +16,18 @@
package org.killbill.billing.payment.core.sm;
-import java.util.List;
-
import org.joda.time.DateTime;
+import org.killbill.automaton.OperationException;
import org.killbill.automaton.State;
import org.killbill.automaton.State.LeavingStateCallback;
-import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionType;
import org.killbill.billing.payment.dao.PaymentModelDao;
import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentDao;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
-import com.google.common.base.Function;
import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
public class RetryLeavingStateCallback implements LeavingStateCallback {
@@ -53,7 +49,7 @@ public class RetryLeavingStateCallback implements LeavingStateCallback {
}
@Override
- public void leavingState(final State state) {
+ public void leavingState(final State state) throws OperationException {
final DateTime utcNow = retryableDirectPaymentAutomatonRunner.clock.getUTCNow();
@@ -69,23 +65,24 @@ public class RetryLeavingStateCallback implements LeavingStateCallback {
if (state.getName().equals(initialState.getName()) ||
state.getName().equals(retriedState.getName())) {
- final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(),
- utcNow, utcNow, stateContext.getDirectPaymentExternalKey(), null,
- stateContext.directPaymentTransactionExternalKey, transactionType, initialState.getName(),
- stateContext.getAmount(), stateContext.getCurrency(),
- stateContext.getPluginName());
-
- final List<PluginPropertyModelDao> properties = ImmutableList.copyOf(Iterables.transform(stateContext.getProperties(), new Function<PluginProperty, PluginPropertyModelDao>() {
- @Override
- public PluginPropertyModelDao apply(final PluginProperty input) {
- // STEPH how to serialize more complex values such as item adjustments. json ?
- final String value = (input.getValue() instanceof String) ? (String) input.getValue() : "TODO: could not serialize";
- return new PluginPropertyModelDao(attempt.getId(), stateContext.getDirectPaymentExternalKey(), stateContext.directPaymentTransactionExternalKey, stateContext.getAccount().getId(),
- stateContext.getPluginName(), input.getKey(), value, stateContext.getCallContext().getUserName(), stateContext.getCallContext().getCreatedDate());
- }
- }));
- retryableDirectPaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, properties, stateContext.internalCallContext);
- stateContext.setAttemptId(attempt.getId());
+ try {
+ final byte [] serializedProperties = PluginPropertySerializer.serialize(stateContext.getProperties());
+
+
+ final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(stateContext.getAccount().getId(), stateContext.getPaymentMethodId(),
+ utcNow, utcNow, stateContext.getDirectPaymentExternalKey(), null,
+ stateContext.directPaymentTransactionExternalKey, transactionType, initialState.getName(),
+ stateContext.getAmount(), stateContext.getCurrency(),
+ stateContext.getPluginName(), serializedProperties);
+
+ retryableDirectPaymentAutomatonRunner.paymentDao.insertPaymentAttemptWithProperties(attempt, stateContext.internalCallContext);
+ stateContext.setAttemptId(attempt.getId());
+
+ } catch (PluginPropertySerializerException e) {
+ // STEPH
+ throw new OperationException(e);
+ }
+
}
}
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
index 8c65ac1..9197bb5 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/DefaultPaymentDao.java
@@ -72,18 +72,7 @@ public class DefaultPaymentDao implements PaymentDao {
}
@Override
- public List<PluginPropertyModelDao> getProperties(final UUID attemptId, final InternalCallContext context) {
- return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<PluginPropertyModelDao>>() {
- @Override
- public List<PluginPropertyModelDao> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
- final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
- return transactional.become(PluginPropertySqlDao.class).getPluginProperties(attemptId.toString());
- }
- });
- }
-
- @Override
- public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final List<PluginPropertyModelDao> properties, final InternalCallContext context) {
+ public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final InternalCallContext context) {
return transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<PaymentAttemptModelDao>() {
@Override
@@ -91,10 +80,6 @@ public class DefaultPaymentDao implements PaymentDao {
final PaymentAttemptSqlDao transactional = entitySqlDaoWrapperFactory.become(PaymentAttemptSqlDao.class);
transactional.create(attempt, context);
final PaymentAttemptModelDao result = transactional.getById(attempt.getId().toString(), context);
-
- // Those calls are not part of history and audit on purpose, this is just to implement a temporary property cache cache
- transactional.become(PluginPropertySqlDao.class).batchCreateFromTransaction(properties);
-
return result;
}
});
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
index 82bba44..815a6d2 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentAttemptModelDao.java
@@ -17,6 +17,7 @@
package org.killbill.billing.payment.dao;
import java.math.BigDecimal;
+import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
@@ -41,12 +42,13 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
private BigDecimal amount;
private Currency currency;
private String pluginName;
+ private byte [] pluginProperties;
public PaymentAttemptModelDao() { /* For the DAO mapper */ }
public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, final UUID id, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType,
- final String stateName, final BigDecimal amount, final Currency currency, final String pluginName) {
+ final String stateName, final BigDecimal amount, final Currency currency, final String pluginName, final byte [] pluginProperties) {
super(id, createdDate, updatedDate);
this.accountId = accountId;
this.paymentMethodId = paymentMethodId;
@@ -58,13 +60,14 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
this.amount = amount;
this.currency = currency;
this.pluginName = pluginName;
+ this.pluginProperties = pluginProperties;
}
public PaymentAttemptModelDao(final UUID accountId, final UUID paymentMethodId, @Nullable final DateTime createdDate, @Nullable final DateTime updatedDate,
final String paymentExternalKey, final UUID transactionId, final String transactionExternalKey, final TransactionType transactionType, final String stateName,
- final BigDecimal amount, final Currency currency, final String pluginName) {
+ final BigDecimal amount, final Currency currency, final String pluginName, final byte [] pluginProperties) {
this(accountId, paymentMethodId, UUID.randomUUID(), createdDate, updatedDate, paymentExternalKey, transactionId, transactionExternalKey, transactionType, stateName,
- amount, currency, pluginName);
+ amount, currency, pluginName, pluginProperties);
}
public String getPaymentExternalKey() {
@@ -107,6 +110,14 @@ public class PaymentAttemptModelDao extends EntityBase implements EntityModelDao
this.pluginName = pluginName;
}
+ public byte [] getPluginProperties() {
+ return pluginProperties;
+ }
+
+ public void setPluginProperties(final byte [] pluginProperties) {
+ this.pluginProperties = pluginProperties;
+ }
+
public UUID getAccountId() {
return accountId;
}
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
index cf0a59e..a41d336 100644
--- a/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PaymentDao.java
@@ -28,10 +28,7 @@ import org.killbill.billing.util.entity.Pagination;
public interface PaymentDao {
-
- public List<PluginPropertyModelDao> getProperties(UUID attenptId, InternalCallContext context);
-
- public PaymentAttemptModelDao insertPaymentAttemptWithProperties(PaymentAttemptModelDao attempt, List<PluginPropertyModelDao> properties, InternalCallContext context);
+ public PaymentAttemptModelDao insertPaymentAttemptWithProperties(PaymentAttemptModelDao attempt, InternalCallContext context);
public void updatePaymentAttempt(UUID paymentAttemptId, UUID transactionId, String state, InternalCallContext context);
diff --git a/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java
new file mode 100644
index 0000000..6ddd3bc
--- /dev/null
+++ b/payment/src/main/java/org/killbill/billing/payment/dao/PluginPropertySerializer.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.dao;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.killbill.billing.payment.api.PluginProperty;
+
+import com.ning.compress.lzf.LZFDecoder;
+import com.ning.compress.lzf.LZFEncoder;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class PluginPropertySerializer {
+
+ private static final int MAX_SIZE_PROPERTIES_BYTES = (8 * 1024); // As defined in payment_attempt ddl
+
+ private static final JsonFactory jsonFactory = new JsonFactory();
+ private static ObjectMapper mapper = new ObjectMapper(jsonFactory);
+
+ static {
+ mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
+ }
+
+ public static byte[] serialize(final Iterable<PluginProperty> input) throws PluginPropertySerializerException {
+
+ final ByteArrayOutputStream out = new ByteArrayOutputStream(MAX_SIZE_PROPERTIES_BYTES);
+ try {
+ final JsonGenerator jsonGenerator = jsonFactory.createGenerator(out, JsonEncoding.UTF8);
+ jsonGenerator.writeStartArray();
+ for (final PluginProperty cur : input) {
+ final String key = cur.getKey();
+ final Object value = cur.getValue();
+ jsonGenerator.writeStartObject();
+ jsonGenerator.writeFieldName(key);
+ mapper.writeValue(jsonGenerator, value);
+ jsonGenerator.writeEndObject();
+ }
+ jsonGenerator.writeEndArray();
+ jsonGenerator.close();
+ final byte[] data = out.toByteArray();
+ return LZFEncoder.encode(data);
+ } catch (final IOException e) {
+ throw new PluginPropertySerializerException(e);
+ }
+ }
+
+ public static Iterable<PluginProperty> deserialize(final byte[] input) throws PluginPropertySerializerException {
+
+ final List<PluginProperty> result = new ArrayList<PluginProperty>();
+ try {
+ final byte[] uncompressed = LZFDecoder.decode(input);
+ final InputStream in = new ByteArrayInputStream(uncompressed);
+ final JsonParser jsonParser = jsonFactory.createParser(in);
+
+ PluginProperty prop = null;
+ String key = null;
+ JsonToken nextToken = jsonParser.nextToken();
+ while (nextToken != null && nextToken != JsonToken.END_ARRAY) {
+ if (nextToken != JsonToken.START_ARRAY) {
+ if (nextToken == JsonToken.FIELD_NAME && key == null) {
+ key = jsonParser.getText();
+ } else if (key != null) {
+ final Object value = mapper.readValue(jsonParser, Object.class);
+ prop = new PluginProperty(key, value, false);
+ key = null;
+ } else if (nextToken == JsonToken.END_OBJECT) {
+ result.add(prop);
+ prop = null;
+ }
+ }
+ nextToken = jsonParser.nextToken();
+ }
+ jsonParser.close();
+ return result;
+ } catch (final UnsupportedEncodingException e) {
+ throw new PluginPropertySerializerException(e);
+ } catch (final JsonParseException e) {
+ throw new PluginPropertySerializerException(e);
+ } catch (final IOException e) {
+ throw new PluginPropertySerializerException(e);
+ }
+ }
+
+ public static class PluginPropertySerializerException extends Exception {
+
+ public PluginPropertySerializerException() {
+ }
+
+ public PluginPropertySerializerException(final String message) {
+ super(message);
+ }
+
+ public PluginPropertySerializerException(final Throwable cause) {
+ super(cause);
+ }
+
+ public PluginPropertySerializerException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
index 3060330..1c4083b 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
+++ b/payment/src/main/resources/org/killbill/billing/payment/dao/PaymentAttemptSqlDao.sql.stg
@@ -17,6 +17,7 @@ tableFields(prefix) ::= <<
, <prefix>amount
, <prefix>currency
, <prefix>plugin_name
+, <prefix>plugin_properties
, <prefix>created_by
, <prefix>created_date
, <prefix>updated_by
@@ -34,6 +35,7 @@ tableValues() ::= <<
, :amount
, :currency
, :pluginName
+, :pluginProperties
, :createdBy
, :createdDate
, :updatedBy
diff --git a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
index afa8c33..0d6a797 100644
--- a/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
+++ b/payment/src/main/resources/org/killbill/billing/payment/ddl.sql
@@ -15,6 +15,7 @@ CREATE TABLE payment_attempts (
amount numeric(15,9),
currency char(3),
plugin_name varchar(50) NOT NULL,
+ plugin_properties blob(8194),
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
updated_by varchar(50) NOT NULL,
@@ -44,6 +45,7 @@ CREATE TABLE payment_attempt_history (
amount numeric(15,9),
currency char(3),
plugin_name varchar(50) NOT NULL,
+ plugin_properties blob(8194),
change_type char(6) NOT NULL,
created_by varchar(50) NOT NULL,
created_date datetime NOT NULL,
@@ -201,22 +203,6 @@ CREATE TABLE transaction_history (
CREATE INDEX transaction_history_target_record_id ON transaction_history(target_record_id);
CREATE INDEX transaction_history_tenant_account_record_id ON transaction_history(tenant_record_id, account_record_id);
-DROP TABLE IF EXISTS payment_plugin_properties;
-CREATE TABLE payment_plugin_properties (
- record_id int(11) unsigned NOT NULL AUTO_INCREMENT,
- attempt_id char(36) NOT NULL,
- payment_external_key varchar(255),
- transaction_external_key varchar(255),
- account_id char(36) NOT NULL,
- plugin_name varchar(50) DEFAULT NULL,
- prop_key varchar(255),
- prop_value varchar(255),
- created_by varchar(50) NOT NULL,
- created_date datetime NOT NULL,
- PRIMARY KEY (record_id)
-) /*! CHARACTER SET utf8 COLLATE utf8_bin */;
-CREATE INDEX payment_plugin_properties_attempt_id ON payment_plugin_properties(attempt_id);
-
/* PaymentControlPlugin lives here until this becomes a first class citizen plugin */
DROP TABLE IF EXISTS _invoice_payment_control_plugin_auto_pay_off;
diff --git a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java
index f053e8f..3c9d8d1 100644
--- a/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java
+++ b/payment/src/test/java/org/killbill/billing/payment/core/sm/TestRetryableDirectPayment.java
@@ -21,7 +21,6 @@ import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
-import javax.annotation.Nullable;
import javax.inject.Named;
import org.joda.time.DateTime;
@@ -46,7 +45,7 @@ import org.killbill.billing.payment.dao.PaymentAttemptModelDao;
import org.killbill.billing.payment.dao.PaymentDao;
import org.killbill.billing.payment.dao.PaymentModelDao;
import org.killbill.billing.payment.dao.PaymentTransactionModelDao;
-import org.killbill.billing.payment.dao.PluginPropertyModelDao;
+import org.killbill.billing.payment.dao.PluginPropertySerializer;
import org.killbill.billing.payment.glue.PaymentModule;
import org.killbill.billing.payment.plugin.api.PaymentPluginApi;
import org.killbill.billing.payment.provider.MockPaymentControlProviderPlugin;
@@ -114,6 +113,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
private final ImmutableList<PluginProperty> emptyProperties = ImmutableList.of();
private final MockPaymentControlProviderPlugin mockRetryProviderPlugin = new MockPaymentControlProviderPlugin();
+ private byte [] EMPTY_PROPERTIES;
private MockRetryableDirectPaymentAutomatonRunner runner;
private RetryableDirectPaymentStateContext directPaymentStateContext;
private MockRetryAuthorizeOperationCallback mockRetryAuthorizeOperationCallback;
@@ -136,6 +136,7 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
return MockPaymentControlProviderPlugin.PLUGIN_NAME;
}
}, mockRetryProviderPlugin);
+ EMPTY_PROPERTIES = PluginPropertySerializer.serialize(ImmutableList.<PluginProperty>of());
}
@BeforeMethod(groups = "fast")
@@ -464,8 +465,8 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
final UUID directTransactionId = UUID.randomUUID();
paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
- TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
- ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+ TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+ internalCallContext
);
runner.run(state,
false,
@@ -512,8 +513,8 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
final UUID directTransactionId = UUID.randomUUID();
paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
- TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
- ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+ TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+ internalCallContext
);
try {
@@ -559,8 +560,8 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
final UUID directTransactionId = UUID.randomUUID();
paymentDao.insertPaymentAttemptWithProperties(new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
- TransactionType.AUTHORIZE, state.getName(), amount, currency, null),
- ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+ TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES),
+ internalCallContext
);
try {
@@ -615,9 +616,9 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
final UUID directPaymentId = UUID.randomUUID();
final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
- TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+ TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
paymentDao.insertPaymentAttemptWithProperties(attempt,
- ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+ internalCallContext
);
paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
new PaymentTransactionModelDao(directTransactionId, directPaymentTransactionExternalKey, utcNow, utcNow, directPaymentId, TransactionType.AUTHORIZE, utcNow, TransactionStatus.PAYMENT_FAILURE, amount, currency, "bla", "foo"),
@@ -657,9 +658,9 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
final UUID directPaymentId = UUID.randomUUID();
final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
- TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+ TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
paymentDao.insertPaymentAttemptWithProperties(attempt,
- ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+ internalCallContext
);
paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
new PaymentTransactionModelDao(directTransactionId, directPaymentTransactionExternalKey, utcNow, utcNow, directPaymentId, TransactionType.AUTHORIZE, utcNow,
@@ -705,9 +706,9 @@ public class TestRetryableDirectPayment extends PaymentTestSuiteNoDB {
final UUID directPaymentId = UUID.randomUUID();
final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(account.getId(), paymentMethodId, utcNow, utcNow,
directPaymentExternalKey, directTransactionId, directPaymentTransactionExternalKey,
- TransactionType.AUTHORIZE, state.getName(), amount, currency, null);
+ TransactionType.AUTHORIZE, state.getName(), amount, currency, null, EMPTY_PROPERTIES);
paymentDao.insertPaymentAttemptWithProperties(attempt,
- ImmutableList.<PluginPropertyModelDao>of(), internalCallContext
+ internalCallContext
);
paymentDao.insertDirectPaymentWithFirstTransaction(new PaymentModelDao(directPaymentId, utcNow, utcNow, account.getId(), paymentMethodId, -1, directPaymentExternalKey),
new PaymentTransactionModelDao(directTransactionId, directPaymentTransactionExternalKey, utcNow, utcNow, directPaymentId, TransactionType.AUTHORIZE, utcNow,
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
index ba4f6cd..ae583ad 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/MockPaymentDao.java
@@ -40,27 +40,19 @@ public class MockPaymentDao implements PaymentDao {
private final Map<UUID, PaymentModelDao> payments = new HashMap<UUID, PaymentModelDao>();
private final Map<UUID, PaymentTransactionModelDao> transactions = new HashMap<UUID, PaymentTransactionModelDao>();
private final Map<UUID, PaymentAttemptModelDao> attempts = new HashMap<UUID, PaymentAttemptModelDao>();
- private final List<PluginPropertyModelDao> properties = new ArrayList<PluginPropertyModelDao>();
public void reset() {
synchronized (this) {
payments.clear();
transactions.clear();
attempts.clear();
- properties.clear();
}
}
@Override
- public List<PluginPropertyModelDao> getProperties(final UUID attemptId, final InternalCallContext context) {
- return properties;
- }
-
- @Override
- public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final List<PluginPropertyModelDao> properties, final InternalCallContext context) {
+ public PaymentAttemptModelDao insertPaymentAttemptWithProperties(final PaymentAttemptModelDao attempt, final InternalCallContext context) {
synchronized (this) {
attempts.put(attempt.getId(), attempt);
- this.properties.addAll(properties);
return attempt;
}
}
@@ -157,7 +149,7 @@ public class MockPaymentDao implements PaymentDao {
}
@Override
- public void updateDirectPaymentAndTransactionOnCompletion(final UUID directPaymentId, final String currentPaymentStateName, final String lastSuccessPaymentStateName, final UUID directTransactionId, final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency, final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
+ public void updateDirectPaymentAndTransactionOnCompletion(final UUID directPaymentId, final String currentPaymentStateName, final String lastSuccessPaymentStateName, final UUID directTransactionId, final TransactionStatus paymentStatus, final BigDecimal processedAmount, final Currency processedCurrency, final String gatewayErrorCode, final String gatewayErrorMsg, final InternalCallContext context) {
synchronized (this) {
final PaymentModelDao payment = payments.get(directPaymentId);
if (payment != null) {
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
index c3a87e6..8077815 100644
--- a/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPaymentDao.java
@@ -24,8 +24,11 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.billing.payment.PaymentTestSuiteWithEmbeddedDB;
+import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.payment.api.TransactionStatus;
import org.killbill.billing.payment.api.TransactionType;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.testng.Assert;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
@@ -36,7 +39,7 @@ import static org.testng.Assert.assertNull;
public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
@Test(groups = "slow")
- public void testPaymentAttempt() {
+ public void testPaymentAttempt() throws PluginPropertySerializerException {
final UUID directTransactionId = UUID.randomUUID();
final String paymentExternalKey = "vraiment?";
final String transactionExternalKey = "tduteuqweq";
@@ -46,43 +49,29 @@ public class TestPaymentDao extends PaymentTestSuiteWithEmbeddedDB {
final UUID accountId = UUID.randomUUID();
- final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(UUID.randomUUID(), UUID.randomUUID(), clock.getUTCNow(), clock.getUTCNow(),
- paymentExternalKey, directTransactionId, transactionExternalKey, transactionType, stateName,
- BigDecimal.ZERO, Currency.ALL, pluginName);
- final PluginPropertyModelDao prop1 = new PluginPropertyModelDao(attempt.getId(), "foo", transactionExternalKey, accountId, "PLUGIN", "key1", "value1", "yo", clock.getUTCNow());
- final PluginPropertyModelDao prop2 = new PluginPropertyModelDao(attempt.getId(), "foo2", transactionExternalKey, accountId, "PLUGIN", "key2", "value2", "yo", clock.getUTCNow());
- final PluginPropertyModelDao prop3 = new PluginPropertyModelDao(UUID.randomUUID() , "foo3", "other", UUID.randomUUID(), "PLUGIN", "key2", "value2", "yo", clock.getUTCNow());
- final List<PluginPropertyModelDao> props = new ArrayList<PluginPropertyModelDao>();
- props.add(prop1);
- props.add(prop2);
- props.add(prop3);
+ final List<PluginProperty> properties = new ArrayList<PluginProperty>();
+ properties.add(new PluginProperty("key1", "value1", false));
+ properties.add(new PluginProperty("key2", "value2", false));
+
+ final byte [] serialized = PluginPropertySerializer.serialize(properties);
+ final PaymentAttemptModelDao attempt = new PaymentAttemptModelDao(UUID.randomUUID(), UUID.randomUUID(), clock.getUTCNow(), clock.getUTCNow(),
+ paymentExternalKey, directTransactionId, transactionExternalKey, transactionType, stateName,
+ BigDecimal.ZERO, Currency.ALL, pluginName, serialized);
- PaymentAttemptModelDao savedAttempt = paymentDao.insertPaymentAttemptWithProperties(attempt, props, internalCallContext);
+ PaymentAttemptModelDao savedAttempt = paymentDao.insertPaymentAttemptWithProperties(attempt, internalCallContext);
assertEquals(savedAttempt.getTransactionExternalKey(), transactionExternalKey);
assertEquals(savedAttempt.getTransactionType(), transactionType);
assertEquals(savedAttempt.getStateName(), stateName);
assertEquals(savedAttempt.getPluginName(), pluginName);
- final List<PluginPropertyModelDao> retrievedProperties = paymentDao.getProperties(attempt.getId(), internalCallContext);
- assertEquals(retrievedProperties.size(), 2);
- assertEquals(retrievedProperties.get(0).getAccountId(), accountId);
- assertEquals(retrievedProperties.get(0).getTransactionExternalKey(), transactionExternalKey);
- assertEquals(retrievedProperties.get(0).getPluginName(), "PLUGIN");
- assertEquals(retrievedProperties.get(0).getPaymentExternalKey(), "foo");
- assertEquals(retrievedProperties.get(0).getPropKey(), "key1");
- assertEquals(retrievedProperties.get(0).getPropValue(), "value1");
- assertEquals(retrievedProperties.get(0).getCreatedBy(), "yo");
-
- assertEquals(retrievedProperties.get(1).getAccountId(), accountId);
- assertEquals(retrievedProperties.get(1).getTransactionExternalKey(), transactionExternalKey);
- assertEquals(retrievedProperties.get(1).getPluginName(), "PLUGIN");
- assertEquals(retrievedProperties.get(1).getPaymentExternalKey(), "foo2");
- assertEquals(retrievedProperties.get(1).getPropKey(), "key2");
- assertEquals(retrievedProperties.get(1).getPropValue(), "value2");
- assertEquals(retrievedProperties.get(1).getCreatedBy(), "yo");
+ final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(savedAttempt.getPluginProperties());
+ int i = 0;
+ for (PluginProperty cur : deserialized) {
+ Assert.assertEquals(cur, properties.get(i++));
+ }
final PaymentAttemptModelDao retrievedAttempt1 = paymentDao.getPaymentAttempt(attempt.getId(), internalCallContext);
assertEquals(retrievedAttempt1.getTransactionExternalKey(), transactionExternalKey);
diff --git a/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java b/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java
new file mode 100644
index 0000000..096936e
--- /dev/null
+++ b/payment/src/test/java/org/killbill/billing/payment/dao/TestPluginPropertySerializer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 Groupon, Inc
+ * Copyright 2014 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.dao;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.killbill.billing.payment.api.PluginProperty;
+import org.killbill.billing.payment.dao.PluginPropertySerializer.PluginPropertySerializerException;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestPluginPropertySerializer {
+
+ @Test(groups = "fast")
+ public void testNoPluginProperty() throws PluginPropertySerializerException {
+ final List<PluginProperty> input = new ArrayList<PluginProperty>();
+
+ final byte[] serialized = PluginPropertySerializer.serialize(input);
+ final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+ int i = 0;
+ for (PluginProperty cur : deserialized) {
+ Assert.assertEquals(cur, input.get(i++));
+ }
+ }
+
+ @Test(groups = "fast")
+ public void testSimplePluginProperty() throws PluginPropertySerializerException {
+ final List<PluginProperty> input = new ArrayList<PluginProperty>();
+ input.add(new PluginProperty("foo", "bar", false));
+
+ final byte[] serialized = PluginPropertySerializer.serialize(input);
+ final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+ int i = 0;
+ for (PluginProperty cur : deserialized) {
+ Assert.assertEquals(cur, input.get(i++));
+ }
+ }
+
+ @Test(groups = "fast")
+ public void testLotsPluginProperty() throws PluginPropertySerializerException {
+ final List<PluginProperty> input = new ArrayList<PluginProperty>();
+ for (int i = 0; i < 100; i++) {
+ input.add(new PluginProperty("foo-" + i, "bar-" + i, false));
+ }
+
+ final byte[] serialized = PluginPropertySerializer.serialize(input);
+ final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+ int i = 0;
+ for (PluginProperty cur : deserialized) {
+ Assert.assertEquals(cur, input.get(i++));
+ }
+ }
+
+ @Test(groups = "fast", enabled = true)
+ public void testPluginPropertyWithComplexValue() throws PluginPropertySerializerException {
+ final HashMap<String, BigDecimal> something = new HashMap<String, BigDecimal>();
+ something.put("yoyo", new BigDecimal("0.0"));
+ something.put("what", new BigDecimal("10.0"));
+ final List<PluginProperty> input = new ArrayList<PluginProperty>();
+ input.add(new PluginProperty("prev", "simple", false));
+ input.add(new PluginProperty("foo", something, false));
+ input.add(new PluginProperty("next", "easy", false));
+
+ final byte[] serialized = PluginPropertySerializer.serialize(input);
+ final Iterable<PluginProperty> deserialized = PluginPropertySerializer.deserialize(serialized);
+ int i = 0;
+ for (PluginProperty cur : deserialized) {
+ if (i == 0 || i == 2) {
+ Assert.assertEquals(cur, input.get(i));
+ } else {
+ Assert.assertEquals(cur.getKey(), "foo");
+ Assert.assertTrue(cur.getValue() instanceof Map);
+ final Map<String, BigDecimal> mappedValue = (Map<String, BigDecimal>) cur.getValue();
+ Assert.assertTrue(mappedValue.containsKey("yoyo"));
+ Assert.assertTrue(mappedValue.containsKey("what"));
+ Assert.assertTrue(mappedValue.get("yoyo").compareTo(BigDecimal.ZERO) == 0);
+ Assert.assertTrue(mappedValue.get("what").compareTo(BigDecimal.TEN) == 0);
+ }
+ i++;
+ }
+
+ }
+}