public class DealConv {

    public static Deal convert(SiteJournalDealsStruct d) {
        return new Deal.Builder()
                .buyerId(d.getDealBuyerId())
                .eventId(d.getDealEventId())
                .eventTime(d.getDealEventTime())
                .dealId(d.getDealId())
                .itemId(d.getDealItemId())
                .quantity(d.getDealQuantity())
                .sellerId(d.getDealSellerId())
                .transactionId(d.getDealTransactionId())
                .dealType(d.getDealEventType())
                .build();
    }
} public class PaymentConv {

    public static Payment convert(PostBuyFormDataStruct f, long sellerId, Map<Integer, String> countries) {

        PostBuyFormAddressStruct receiverData = f.getPostBuyFormShipmentAddress();
        PostBuyFormAddressStruct ordererData = f.getPostBuyFormInvoiceData();
        fillMissingFields(f, receiverData, ordererData);

        Address receiver = convertAddress(receiverData, countries);
        Address orderer = convertAddress(ordererData, countries);

        List<Item> items = f.getPostBuyFormItems()
                .getItem()
                .stream()
                .flatMap(i -> ItemConv.convert(i, f.getPostBuyFormId(), sellerId).stream())
                .collect(toList());

        return new Payment.Builder()
                .transactionId(f.getPostBuyFormId())
                .buyerId(f.getPostBuyFormBuyerId())
                .sellerId(sellerId)
                .email(f.getPostBuyFormBuyerEmail())
                .date(f.getPostBuyFormDateInit())
                .amount(f.getPostBuyFormAmount())
                .postageAmount(f.getPostBuyFormPostageAmount())
                .paymentAmount(f.getPostBuyFormPaymentAmount())
                .withInvoice(f.getPostBuyFormInvoiceOption())
                .msg(f.getPostBuyFormMsgToSeller())
                .payId(f.getPostBuyFormPayId())
                .payStatus(f.getPostBuyFormPayStatus())
                .shipmentId(f.getPostBuyFormShipmentId())
                .receiver(receiver)
                .orderer(orderer)
                .items(items)
                .build();
    }

    private static void fillMissingFields(PostBuyFormDataStruct f, PostBuyFormAddressStruct receiverData, PostBuyFormAddressStruct ordererData) {
        if (ordererData.getPostBuyFormAdrPhone().isEmpty() && !receiverData.getPostBuyFormAdrPhone().isEmpty() && f.getPostBuyFormInvoiceOption() == 1) {
            ordererData.setPostBuyFormAdrPhone(receiverData.getPostBuyFormAdrPhone());
        }
    }

    private static Address convertAddress(PostBuyFormAddressStruct address, Map<Integer, String> countries) {
        return AddressConv.convert(
                address,
                countries.get(address.getPostBuyFormAdrCountry())
        );
    }
} public class InfoService extends AbstractService {

    private static final String COUNTRIES_CACHE_KEY = "COUNTRIES_CACHE_KEY";
    private static final String SHIPPING_CACHE_KEY = "SHIPPING_CACHE_KEY";
    private LoadingCache<String, Map<Integer, String>> cache;

    public InfoService(ServicePort allegro, Credentials cred, Configuration conf) {
        super(allegro, cred, conf);
        this.cache = CacheBuilder.newBuilder()
                .maximumSize(2)
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .build(getLoader());
    }

    private CacheLoader<String, Map<Integer, String>> getLoader() {
        return new CacheLoader<String, Map<Integer, String>>() {
            @Override
            public Map<Integer, String> load(String key) throws Exception {
                switch (key) {
                    case COUNTRIES_CACHE_KEY:
                        return retriveCountries();
                    case SHIPPING_CACHE_KEY:
                        return retriveShipping();
                    default:
                        throw new IllegalArgumentException("Cannot find cache with key " + key);
                }
            }
        };
    }

    public Map<Integer, String> getCountries() {
        return cache.getUnchecked(COUNTRIES_CACHE_KEY);
    }

    public Map<Integer, String> getShipping() {
        return cache.getUnchecked(SHIPPING_CACHE_KEY);
    }

    /**
     * http://allegro.pl/webapi/documentation.php/show/id,25#method-output
     */
    private Map<Integer, String> retriveCountries() {
        DoGetCountriesRequest req = new DoGetCountriesRequest(conf.getCountryId(), cred.getKey());
        DoGetCountriesResponse res = AllegroExecutor.execute(() -> allegro.doGetCountries(req));
        return Collections.unmodifiableMap(
                res.getCountryArray().getItem()
                        .stream()
                        .collect(toMap(CountryInfoType::getCountryId, CountryInfoType::getCountryName))
        );
    }

    /**
     * http://allegro.pl/webapi/documentation.php/show/id,624#method-output
     */
    private Map<Integer,String> retriveShipping() {
        DoGetShipmentDataRequest req = new DoGetShipmentDataRequest(conf.getCountryId(), cred.getKey());
        DoGetShipmentDataResponse res = AllegroExecutor.execute(() -> allegro.doGetShipmentData(req));
        return Collections.unmodifiableMap(
                res.getShipmentDataList().getItem()
                        .stream()
                        .collect(toMap(ShipmentDataStruct::getShipmentId, ShipmentDataStruct::getShipmentName))
        );
    }

} public class Deal {

    private long eventId;
    private DealType dealType;
    private Date eventTime;
    private long dealId;
    private Optional<Long> transactionId;
    private long sellerId;
    private long itemId;
    private int buyerId;
    private int quantity;

    public Deal() { }

    private Deal(Builder builder) {
        eventId = builder.eventId;
        dealType = builder.dealType;
        eventTime = builder.eventTime;
        dealId = builder.dealId;
        transactionId = builder.transactionId;
        sellerId = builder.sellerId;
        itemId = builder.itemId;
        buyerId = builder.buyerId;
        quantity = builder.quantity;
    }


    public long getEventId() {
        return eventId;
    }
    public DealType getDealType() {
        return dealType;
    }
    public Date getEventTime() {
        return eventTime;
    }
    public long getDealId() {
        return dealId;
    }
    public Optional<Long> getTransactionId() {
        return transactionId;
    }
    public long getSellerId() {
        return sellerId;
    }
    public long getItemId() {
        return itemId;
    }
    public int getBuyerId() {
        return buyerId;
    }
    public int getQuantity() {
        return quantity;
    }

    public static final class Builder {
        private long eventId;
        private DealType dealType;
        private Date eventTime;
        private long dealId;
        private Optional<Long> transactionId;
        private long sellerId;
        private long itemId;
        private int buyerId;
        private int quantity;

        public Builder() {
        }

        public Builder eventId(long eventId) {
            this.eventId = eventId;
            return this;
        }

        public Builder dealType(int dealType) {
            this.dealType = DealType.VALUES.get(dealType);
            return this;
        }

        public Builder eventTime(long eventTime) {
            this.eventTime = UnixDate.toDate(eventTime);
            return this;
        }

        public Builder dealId(long dealId) {
            this.dealId = dealId;
            return this;
        }

        public Builder transactionId(long transactionId) {
            if (transactionId == 0) {
                this.transactionId = Optional.empty();
            } else {
                this.transactionId = Optional.of(transactionId);
            }
            return this;
        }

        public Builder sellerId(long sellerId) {
            this.sellerId = sellerId;
            return this;
        }

        public Builder itemId(long itemId) {
            this.itemId = itemId;
            return this;
        }

        public Builder buyerId(int buyerId) {
            this.buyerId = buyerId;
            return this;
        }

        public Builder quantity(int quantity) {
            this.quantity = quantity;
            return this;
        }

        public Deal build() {
            return new Deal(this);
        }
    }

    @Override
    public String toString() {
        return "Deal{" +
                "eventId=" + eventId +
                ", dealType=" + dealType +
                ", eventTime=" + eventTime +
                ", dealId=" + dealId +
                ", transactionId=" + transactionId +
                ", itemId=" + itemId +
                ", buyerId=" + buyerId +
                ", quantity=" + quantity +
                '}';
    }
} public enum DealType {

    DEAL(1), TRANSACTION(2), CANCELLATION(3), PAYMENT(4);

    private int type;

    DealType(int type) {
        this.type = type;
    }

    public int getType() {
        return type;
    }

    public static final Map<Integer, DealType> VALUES;
    static {
        VALUES = Collections.unmodifiableMap(
                Arrays.stream(DealType.values())
                        .collect(Collectors.toMap((DealType v) -> v.type, v -> v))
        );
    }

} public class Payment {

    private long transactionId;
    private long buyerId;
    private long sellerId;

    private String email;
    private Date date;

    private double amount;
    private double postageAmount;
    private double paymentAmount;

    private Boolean withInvoice;
    private Optional<String> msg;
    private long payId;
    private String payStatus;
    private Shipment shipment;

    private Address orderer;
    private Address receiver;

    private List<Item> items;

    private boolean processed;

    public Payment() { }

    private Payment(Builder builder) {
        transactionId = builder.transactionId;
        buyerId = builder.buyerId;
        sellerId = builder.sellerId;
        email = builder.email;
        amount = builder.amount;
        date = builder.date;
        postageAmount = builder.postageAmount;
        paymentAmount = builder.paymentAmount;
        withInvoice = builder.withInvoice;
        msg = builder.msg;
        payId = builder.payId;
        payStatus = builder.payStatus;
        shipment = builder.shipment;
        orderer = builder.orderer;
        receiver = builder.receiver;
        items = builder.items;
        processed = builder.processed;
    }

    public long getTransactionId() {
        return transactionId;
    }
    public long getBuyerId() {
        return buyerId;
    }
    public String getEmail() {
        return email;
    }
    public Date getDate() {
        return date;
    }
    public double getAmount() {
        return amount;
    }
    public double getPostageAmount() {
        return postageAmount;
    }
    public double getPaymentAmount() {
        return paymentAmount;
    }
    public Boolean getWithInvoice() {
        return withInvoice;
    }
    public Optional<String> getMsg() {
        return msg;
    }
    public long getPayId() {
        return payId;
    }
    public String getPayStatus() {
        return payStatus;
    }
    public Shipment getShipment() {
        return shipment;
    }
    public Address getReceiver() {
        return receiver;
    }
    public Address getOrderer() {
        return orderer;
    }
    public List<Item> getItems() {
        return items;
    }
    public boolean isProcessed() {
        return processed;
    }

    public long getSellerId() {
        return sellerId;
    }

    public static final class Builder {
        private long buyerId;
        private long sellerId;
        private String email;
        private Date date;
        private double amount;
        private double postageAmount;
        private double paymentAmount;
        private Boolean withInvoice;
        private Optional<String> msg;
        private long payId;
        private String payStatus;
        private Shipment shipment;
        private Address orderer;
        private Address receiver;
        private long transactionId;
        private List<Item> items;
        private boolean processed;

        public Builder() {
        }

        public Builder buyerId(long buyerId) {
            this.buyerId = buyerId;
            return this;
        }

        public Builder email(String email) {
            this.email = email;
            return this;
        }

        public Builder date(String date) {
            try {
                this.date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date);
            } catch (ParseException e) {
                throw new IllegalArgumentException(e.getMessage(), e);
            }
            return this;
        }

        public Builder amount(float amount) {
            this.amount = new BD(amount).doubleValue();
            return this;
        }

        public Builder postageAmount(float postageAmount) {
            this.postageAmount = new BD(postageAmount).doubleValue();
            return this;
        }

        public Builder paymentAmount(float paymentAmount) {
            this.paymentAmount = new BD(paymentAmount).doubleValue();;
            return this;
        }

        public Builder msg(Optional<String> msg) {
            this.msg = msg;
            return this;
        }

        public Builder withInvoice(int withInvoice) {
            this.withInvoice = withInvoice == 1;
            return this;
        }

        public Builder msg(String msg) {
            if (msg.isEmpty()) {
                this.msg = Optional.empty();
            } else {
                this.msg = Optional.of(msg);
            }
            return this;
        }

        public Builder payId(long payId) {
            this.payId = payId;
            return this;
        }

        public Builder payStatus(String payStatus) {
            this.payStatus = payStatus;
            return this;
        }

        public Builder shipmentId(int shipmentId) {
            this.shipment = Shipment.VALUES.get(shipmentId);
            return this;
        }

        public Builder orderer(Address orderer) {
            this.orderer = orderer;
            return this;
        }

        public Builder receiver(Address receiver) {
            this.receiver = receiver;
            return this;
        }

        public Builder transactionId(long transactionId) {
            this.transactionId = transactionId;
            return this;
        }

        public Builder items(List<Item> items) {
            this.items = items;
            return this;
        }

        public Builder processed(boolean processed) {
            this.processed = processed;
            return this;
        }

        public Builder sellerId(long sellerId) {
            this.sellerId = sellerId;
            return this;
        }

        public Payment build() {
            if (!this.withInvoice) {
                this.orderer = receiver;
            }
            return new Payment(this);
        }

    }

    @Override
    public String toString() {
        return "Payment{" +
                "transactionId=" + transactionId +
                ", email='" + email + '\'' +
                ", amount=" + amount +
                ", paymentAmount=" + paymentAmount +
                ", withInvoice=" + withInvoice +
                ", payStatus='" + payStatus + '\'' +
                ", items.size='" + (items != null ? items.size() : 0) + '\'' +
                ", processed=" + processed +
                '}';
    }

} public class AllegroExecutor {

    private static final AllegroExceptionConventer aec = new AllegroExceptionConventer();

    public static <T> T execute(Supplier<T> task) {
        try {
            return task.get();
        } catch (SOAPFaultException e) {
            throw aec.convertException(e.getFault().getFaultCode(), e);
        }
    }
} public class Credentials {

    private long clientId;
    private String username;
    private String passoword;
    private String key;

    @Nullable
    private String restClientId;
    @Nullable
    private String restClientSecret;
    @Nullable
    private String restClientApiKey;
    @Nullable
    private String restRedirectUri;

    public Credentials(long clientId, String username, String passowrd, String key) {
        this(clientId, username, passowrd, key, null, null, null, null);
    }

    public Credentials(long clientId, String username, String passowrd, String key, @Nullable String restClientId, @Nullable String restClientSecret, @Nullable String restClientApiKey, @Nullable String restRedirectUri) {
        this.clientId = clientId;
        this.username = username;
        this.passoword = passowrd;
        this.key = key;
        this.restClientId = restClientId;
        this.restClientSecret = restClientSecret;
        this.restClientApiKey = restClientApiKey;
        this.restRedirectUri = restRedirectUri;
    }

    public long getClientId() {
        return clientId;
    }
    String getUsername() {
        return username;
    }
    String getPassoword() {
        return passoword;
    }
    public String getKey() {
        return key;
    }
    @Nullable
    public String getRestClientId() {
        return restClientId;
    }
    @Nullable
    public String getRestClientSecret() {
        return restClientSecret;
    }
    @Nullable
    public String getRestClientApiKey() {
        return restClientApiKey;
    }
    @Nullable
    public String getRestRedirectUri() {
        return restRedirectUri;
    }

    @Override
    public String toString() {
        return "Credentials{" +
                "clientId='" + clientId + "\', " +
                "username='" + username + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Credentials that = (Credentials) o;

        if (clientId != that.clientId) return false;
        if (!username.equals(that.username)) return false;
        if (!passoword.equals(that.passoword)) return false;
        if (!key.equals(that.key)) return false;
        if (restClientId != null ? !restClientId.equals(that.restClientId) : that.restClientId != null) return false;
        if (restClientSecret != null ? !restClientSecret.equals(that.restClientSecret) : that.restClientSecret != null) return false;
        if (restClientApiKey != null ? !restClientApiKey.equals(that.restClientApiKey) : that.restClientApiKey != null) return false;
        return restRedirectUri != null ? restRedirectUri.equals(that.restRedirectUri) : that.restRedirectUri == null;
    }

    @Override
    public int hashCode() {
        int result = (int) (clientId ^ (clientId >>> 32));
        result = 31 * result + username.hashCode();
        result = 31 * result + passoword.hashCode();
        result = 31 * result + key.hashCode();
        result = 31 * result + (restClientId != null ? restClientId.hashCode() : 0);
        result = 31 * result + (restClientSecret != null ? restClientSecret.hashCode() : 0);
        result = 31 * result + (restClientApiKey != null ? restClientApiKey.hashCode() : 0);
        result = 31 * result + (restRedirectUri != null ? restRedirectUri.hashCode() : 0);
        return result;
    }

} public abstract class AbstractAllegroIterator<T> implements Iterator<T> {

    protected String session;
    protected long startingPoint;
    private List<T> items;
    private boolean fetched;
    private int index;

    public AbstractAllegroIterator(String session, long startingPoint) {
        this.session = session;
        this.startingPoint = startingPoint;
        this.items = new ArrayList<>();
    }

    private boolean isFetchNeeded() {
        return !fetched || index == items.size();
    }

    @Override
    public boolean hasNext() {
        if (isFetchNeeded()) {
            fetch();
        }
        return index < items.size();
    }

    @Override
    public T next() {
        if (isFetchNeeded()) {
            fetch();
        }
        return items.get(index++);
    }

    private void fetch() {
        this.fetched = true;
        this.startingPoint = getStartingPoint();
        this.index = 0;
        this.items = doFetch();
    }

    private long getStartingPoint() {
        return items.size() == 0
                ? startingPoint
                : getItemId(items.get(items.size() - 1));
    }

    protected abstract List<T> doFetch();
    protected abstract long getItemId(T t);

} public class AbstractService {

    protected ServicePort allegro;
    protected Credentials cred;
    protected Configuration conf;

    public AbstractService() {
    }

    public AbstractService(ServicePort allegro, Credentials cred, Configuration conf) {
        this.allegro = allegro;
        this.cred = cred;
        this.conf = conf;
    }

} public class Configuration {

    private int countryId;

    public Configuration(int countryId) {
        this.countryId = countryId;
    }

    public int getCountryId() {
        return countryId;
    }
}
import com.apwglobal.nice.conv.DealConv;
import com.apwglobal.nice.conv.PaymentConv;
import com.apwglobal.nice.country.InfoService;
import com.apwglobal.nice.domain.Deal;
import com.apwglobal.nice.domain.DealType;
import com.apwglobal.nice.domain.Payment;
import com.apwglobal.nice.exception.AllegroExecutor;
import com.apwglobal.nice.login.Credentials;
import com.apwglobal.nice.service.AbstractAllegroIterator;
import com.apwglobal.nice.service.AbstractService;
import com.apwglobal.nice.service.Configuration;
import pl.allegro.webapi.*;
import rx.Observable;
import java.util.List;
import java.util.Optional;
import static java.util.stream.Collectors.toList;
package com.apwglobal.nice.deal;




public class DealService extends AbstractService {

    private InfoService infoService;

    public DealService(ServicePort allegro, Credentials cred, Configuration conf, InfoService countryService) {
        super(allegro, cred, conf);
        this.infoService = countryService;
    }

    public Observable<Deal> getDeals(String session, long startingPoint) {
        return Observable.from(() -> new DealsIterator(session, startingPoint));
    }

    public Observable<Payment> getPayments(String session, long sellerId, Observable<Deal> deals) {
        Observable<Long> transactionsIds = deals
                .filter(d -> d.getTransactionId().isPresent())
                .filter(d -> d.getDealType().equals(DealType.PAYMENT))