/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils.paymentrequest; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import nl.strohalm.cyclos.entities.access.Channel; import nl.strohalm.cyclos.entities.accounts.transactions.PaymentRequestTicket; import nl.strohalm.cyclos.entities.accounts.transactions.Ticket; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.services.access.ChannelServiceLocal; import nl.strohalm.cyclos.services.alerts.ErrorLogServiceLocal; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.services.transactions.PaymentRequestHandler; import nl.strohalm.cyclos.services.transactions.TicketServiceLocal; import nl.strohalm.cyclos.utils.TransactionHelper; import nl.strohalm.cyclos.utils.WorkerThreads; import nl.strohalm.cyclos.utils.cache.CacheListener; import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData; import nl.strohalm.cyclos.utils.transaction.TransactionCommitListener; import nl.strohalm.cyclos.webservices.external.ExternalWebServiceHelper; import nl.strohalm.cyclos.webservices.external.paymentrequest.PaymentRequestWebService; import nl.strohalm.cyclos.webservices.model.PaymentRequestTicketVO; import nl.strohalm.cyclos.webservices.utils.TicketHelper; import org.apache.commons.lang.StringUtils; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; /** * Implementation for payment request handler that invokes the PaymentRequestWebService * * @author luis */ public class PaymentRequestHandlerImpl implements PaymentRequestHandler, BeanFactoryAware, DisposableBean { /** * A payment request handler which never sends a payment request * @author luis */ private static class OfflineHandler implements PaymentRequestWebService { @Override public boolean requestPayment(final String cyclosId, final PaymentRequestTicketVO ticket) { return false; } } private class PaymentRequestSenderThreads extends WorkerThreads<PaymentRequestTicket> { protected PaymentRequestSenderThreads(final String name, final int threadCount) { super(name, threadCount); } @Override protected void process(final PaymentRequestTicket t) { transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { final PaymentRequestTicket ticket = ticketService.load(t.getTicket(), Ticket.Relationships.FROM, Ticket.Relationships.TO); final LocalSettings localSettings = settingsService.getLocalSettings(); final Channel channel = channelService.load(ticket.getToChannel().getId()); try { final PaymentRequestWebService ws = proxyFor(channel); final boolean result = ws.requestPayment(localSettings.getCyclosId(), ticketHelper.toVO(ticket, channel.getPrincipalCustomFields())); if (!result) { throw new Exception("The PaymentRequestWebService returned an error status"); } } catch (final Exception e) { ticketService.markAsFailedtoSend(ticket); final Map<String, String> params = new HashMap<String, String>(); params.put("ticket", ticket.getTicket()); params.put("payer username", ticket.getFrom().getUsername()); params.put("receiver username", ticket.getTo().getUsername()); errorLogService.insert(e, channel.getPaymentRequestWebServiceUrl(), params); } } }); } } private TicketHelper ticketHelper; private SettingsServiceLocal settingsService; private TicketServiceLocal ticketService; private ErrorLogServiceLocal errorLogService; private ChannelServiceLocal channelService; private BeanFactory beanFactory; private boolean initialized; private PaymentRequestSenderThreads senderThreads; private int maxThreads = 5; private TransactionHelper transactionHelper; /** This map is cached, but we do have a {@link CacheListener} to make it consistent in a cluster */ private final Map<String, PaymentRequestWebService> cachedProxies = new ConcurrentHashMap<String, PaymentRequestWebService>(); @Override public void destroy() throws Exception { if (senderThreads != null) { senderThreads.interrupt(); senderThreads = null; } } @Override public void invalidateCache() { cachedProxies.clear(); } @Override public void sendRequest(final PaymentRequestTicket ticket) { maybeInitialize(); if (senderThreads == null) { return; } CurrentTransactionData.addTransactionCommitListener(new TransactionCommitListener() { @Override public void onTransactionCommit() { senderThreads.enqueue(ticket); } }); } @Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } public void setChannelServiceLocal(final ChannelServiceLocal channelService) { this.channelService = channelService; } public void setMaxThreads(final int maxThreads) { this.maxThreads = maxThreads; } private synchronized void maybeInitialize() { // As the standard setter injection was causing problems with other beans, for recursive injection, // the setter injection is no longer used here. if (!initialized) { settingsService = beanFactory.getBean(SettingsServiceLocal.class); ticketHelper = beanFactory.getBean(TicketHelper.class); errorLogService = beanFactory.getBean(ErrorLogServiceLocal.class); ticketService = beanFactory.getBean(TicketServiceLocal.class); transactionHelper = beanFactory.getBean(TransactionHelper.class); senderThreads = new PaymentRequestSenderThreads("Payment request sender for " + settingsService.getLocalSettings().getApplicationName(), maxThreads); initialized = true; } } private PaymentRequestWebService proxyFor(final Channel channel) throws IOException { final String url = StringUtils.trimToEmpty(channel.getPaymentRequestWebServiceUrl()); PaymentRequestWebService proxy = cachedProxies.get(url); if (proxy == null) { // Create the proxy if (url.isEmpty()) { // No payment request URL. Assume the system is offline proxy = new OfflineHandler(); } else { // Create the proxy proxy = ExternalWebServiceHelper.proxyFor(PaymentRequestWebService.class, url); } // Store on cache cachedProxies.put(url, proxy); } return proxy; } }