nl.strohalm.cyclos.services.application.ApplicationServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.services.application.ApplicationServiceImpl.java

Source

/*
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.services.application;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import nl.strohalm.cyclos.dao.ApplicationDAO;
import nl.strohalm.cyclos.dao.IndexOperationDAO;
import nl.strohalm.cyclos.entities.Application;
import nl.strohalm.cyclos.entities.Application.PasswordHash;
import nl.strohalm.cyclos.entities.IndexOperation.EntityType;
import nl.strohalm.cyclos.entities.IndexStatus;
import nl.strohalm.cyclos.entities.Indexable;
import nl.strohalm.cyclos.entities.access.SessionQuery;
import nl.strohalm.cyclos.entities.accounts.LockedAccountsOnPayments;
import nl.strohalm.cyclos.entities.accounts.SystemAccountOwner;
import nl.strohalm.cyclos.entities.accounts.transactions.Invoice;
import nl.strohalm.cyclos.entities.accounts.transactions.InvoiceQuery;
import nl.strohalm.cyclos.entities.alerts.Alert;
import nl.strohalm.cyclos.entities.alerts.SystemAlert;
import nl.strohalm.cyclos.entities.alerts.SystemAlert.Alerts;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.members.messages.MessageBox;
import nl.strohalm.cyclos.entities.members.messages.MessageQuery;
import nl.strohalm.cyclos.initializations.LocalInitialization;
import nl.strohalm.cyclos.scheduling.PollingTasksHandler;
import nl.strohalm.cyclos.scheduling.SchedulingHandler;
import nl.strohalm.cyclos.scheduling.polling.PollingTask;
import nl.strohalm.cyclos.services.InitializingService;
import nl.strohalm.cyclos.services.access.AccessServiceLocal;
import nl.strohalm.cyclos.services.accounts.rates.RateServiceLocal;
import nl.strohalm.cyclos.services.alerts.AlertServiceLocal;
import nl.strohalm.cyclos.services.alerts.ErrorLogServiceLocal;
import nl.strohalm.cyclos.services.elements.MessageServiceLocal;
import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal;
import nl.strohalm.cyclos.services.transactions.InvoiceServiceLocal;
import nl.strohalm.cyclos.utils.MessageResolver;
import nl.strohalm.cyclos.utils.MessageResourcesLoadedListener;
import nl.strohalm.cyclos.utils.TransactionHelper;
import nl.strohalm.cyclos.utils.access.LoggedUser;
import nl.strohalm.cyclos.utils.cache.Cache;
import nl.strohalm.cyclos.utils.cache.CacheCallback;
import nl.strohalm.cyclos.utils.cache.CacheManager;
import nl.strohalm.cyclos.utils.instance.InstanceHandler;
import nl.strohalm.cyclos.utils.lucene.IndexHandler;
import nl.strohalm.cyclos.utils.query.PageHelper;
import nl.strohalm.cyclos.utils.tasks.TaskRunner;
import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData;
import nl.strohalm.cyclos.utils.transaction.TransactionCommitListener;
import nl.strohalm.cyclos.utils.validation.ValidationException;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;

/**
 * Implementation class for the application service interface.
 * @author rafael
 */
public class ApplicationServiceImpl implements ApplicationServiceLocal, ApplicationContextAware, InitializingBean,
        MessageResourcesLoadedListener {

    private static final Log LOG = LogFactory.getLog(ApplicationServiceImpl.class);

    private static final String APPLICATION_CACHE_KEY = "application";
    private static final String MINOR_VERSION_TAG = "_minor_";
    private static final String minorVersion;

    private static String extract(final String cvsTagName) {
        if (cvsTagName == null) {
            return "";
        }

        String result = cvsTagName;
        result = result.replaceAll("\\$Name:", "");
        result = result.replaceAll("\\$", "");
        result = result.replaceAll(" ", "");

        if (StringUtils.isBlank(result) || !result.contains(MINOR_VERSION_TAG)) {
            return "";
        } else {
            String[] tmp = StringUtils.splitByWholeSeparator(result, MINOR_VERSION_TAG);
            return tmp[tmp.length - 1];
        }
    }

    private MessageResolver messageResolver;

    static {
        // the parameter is interpreted by CVS and expanded
        minorVersion = extract("$Name: not supported by cvs2svn $");
    }

    private boolean initialized;
    private boolean initializing;
    private boolean runScheduling;
    private ApplicationContext applicationContext;
    private AccessServiceLocal accessService;
    private AlertServiceLocal alertService;
    private MessageServiceLocal messageService;
    private InvoiceServiceLocal invoiceService;
    private ErrorLogServiceLocal errorLogService;
    private RateServiceLocal rateService;
    private ApplicationDAO applicationDao;
    private IndexOperationDAO indexOperationDao;
    private SchedulingHandler schedulingHandler;
    private PollingTasksHandler pollingTasksHandler;
    private InstanceHandler instanceHandler;
    private IndexHandler indexHandler;
    private CacheManager cacheManager;
    private TaskRunner taskRunner;
    private long startupTime;
    private Properties cyclosProperties;
    private TransactionHelper transactionHelper;
    private LockedAccountsOnPayments lockedAccountsOnPayments;
    private final HashMap<Alerts, String> deferredEvents = new HashMap<Alerts, String>();

    private PermissionServiceLocal permissionService;

    @Override
    public void afterPropertiesSet() throws Exception {
        try {
            lockedAccountsOnPayments = LockedAccountsOnPayments.valueOf(cyclosProperties
                    .getProperty("cyclos.lockedAccountsOnPayments", LockedAccountsOnPayments.ORIGIN.name())
                    .toUpperCase());
        } catch (Exception e) {
            StringBuilder message = new StringBuilder();
            message.append("Invalid value for cyclos.lockedAccountsOnPayments: ")
                    .append(cyclosProperties.getProperty("cyclos.lockedAccountsOnPayments"))
                    .append(". Valid values are ");
            boolean first = true;
            for (LockedAccountsOnPayments item : LockedAccountsOnPayments.values()) {
                if (first) {
                    first = false;
                } else {
                    message.append(", ");
                }
                message.append(item.name().toLowerCase());
            }
            throw new IllegalArgumentException(message.toString());
        }
    }

    @Override
    public void awakePollingTask(final Class<? extends PollingTask> type) {
        if (pollingTasksHandler != null) {
            PollingTask pollingTask = pollingTasksHandler.get(type);
            if (pollingTask != null) {
                pollingTask.awake();
            }
        }
    }

    @Override
    public void awakePollingTaskOnTransactionCommit(final Class<? extends PollingTask> type) {
        CurrentTransactionData.addTransactionCommitListener(new TransactionCommitListener() {
            @Override
            public void onTransactionCommit() {
                awakePollingTask(type);
            }
        });
    }

    @Override
    public Calendar getAccountStatusEnabledSince() {
        return getApplication().getAccountStatusEnabledSince();
    }

    @Override
    public ApplicationStatusVO getApplicationStatus() {
        final ApplicationStatusVO vo = new ApplicationStatusVO();

        // Uptime period
        final long diff = System.currentTimeMillis() - startupTime;
        final int days = (int) (diff / DateUtils.MILLIS_PER_DAY);
        final int hours = (int) ((diff % DateUtils.MILLIS_PER_DAY) / DateUtils.MILLIS_PER_HOUR);
        vo.setUptimeDays(days);
        vo.setUptimeHours(hours);

        // Connected users
        SessionQuery sessions = new SessionQuery();
        sessions.setGroups(permissionService.getAllVisibleGroups());
        sessions.setPageForCount();

        sessions.setNatures(Collections.singleton(Group.Nature.ADMIN));
        vo.setConnectedAdmins(PageHelper.getTotalCount(accessService.searchSessions(sessions)));

        sessions.setNatures(Collections.singleton(Group.Nature.MEMBER));
        vo.setConnectedMembers(PageHelper.getTotalCount(accessService.searchSessions(sessions)));

        sessions.setNatures(Collections.singleton(Group.Nature.BROKER));
        vo.setConnectedBrokers(PageHelper.getTotalCount(accessService.searchSessions(sessions)));

        sessions.setNatures(Collections.singleton(Group.Nature.OPERATOR));
        vo.setConnectedOperators(PageHelper.getTotalCount(accessService.searchSessions(sessions)));

        // Cyclos version
        vo.setCyclosVersion(getCyclosVersion());

        // Number of alerts
        vo.setMemberAlerts(alertService.getAlertCount(Alert.Type.MEMBER));
        vo.setSystemAlerts(alertService.getAlertCount(Alert.Type.SYSTEM));
        vo.setErrors(errorLogService.getCount());

        // Unread messages
        vo.setUnreadMessages(countUnreadMessages());

        // Open invoices
        vo.setOpenInvoices(countOpenInvoices());

        return vo;
    }

    @Override
    public String getCyclosVersion() {
        String suffix = "";
        if (StringUtils.isNotBlank(minorVersion)) {
            suffix = " (" + minorVersion + ")";
        }
        return getApplication().getVersion() + suffix;
    }

    @Override
    public Map<Class<? extends Indexable>, IndexStatus> getFullTextIndexesStatus() {
        final Map<Class<? extends Indexable>, IndexStatus> stats = new LinkedHashMap<Class<? extends Indexable>, IndexStatus>();
        for (EntityType type : EntityType.values()) {
            Class<? extends Indexable> entityClass = type.getEntityClass();
            stats.put(entityClass, indexHandler.getIndexStatus(entityClass));
        }
        return stats;
    }

    @Override
    public LockedAccountsOnPayments getLockedAccountsOnPayments() {
        return lockedAccountsOnPayments;
    }

    public MessageResolver getMessageResolver() {
        return messageResolver;
    }

    @Override
    public PasswordHash getPasswordHash() {
        return getApplication().getPasswordHash();
    }

    @Override
    public synchronized void initialize() {
        if (initialized || initializing) {
            return;
        }
        initializing = true;
        try {
            // Store the startup time
            startupTime = System.currentTimeMillis();

            // Set AWT to headless mode. This way we can use XFree86 libs
            // on *nix systems without an X server running.
            setHeadlessMode();

            // The InitializingServices should be only initialized when scheduling is used on this cyclos instance.
            runScheduling = !Boolean.valueOf(cyclosProperties.getProperty("cyclos.disableScheduling", "false"));
            if (runScheduling) {
                // Run the initializations (beans of type InitializingService)
                Set<String> beanNames = applicationContext.getBeansOfType(InitializingService.class).keySet();
                taskRunner.runInitializations(beanNames);

                // Start both scheduling and polling tasks handlers
                schedulingHandler.start();
                pollingTasksHandler.start();
            }

            // Run the initializations
            Collection<LocalInitialization> initializations = applicationContext
                    .getBeansOfType(LocalInitialization.class).values();
            runAll(initializations);

            if (runScheduling) {
                // Create a system alert
                transactionHelper.runAsync(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(final TransactionStatus status) {
                        // at this point, the resource bundle for translations is not loaded yet
                        // thus, we need to defer the creation of the event
                        deferEvent(SystemAlert.Alerts.APPLICATION_RESTARTED, instanceHandler.getId());
                    }
                });
            }
            initialized = true;
            // add this object as a listener to the messageResolver, which will inform (notify) this
            // object when the translation resource bundles have been loaded, so that finally alerts can be created
            messageResolver.addMessageResourcesLoadedListener(this);
        } finally {
            initializing = false;
        }
    }

    @Override
    public boolean isInitialized() {
        return initialized && !initializing;
    }

    @Override
    public boolean isOnline() {
        Application app = getApplication();
        return app != null && app.isOnline();
    }

    @Override
    public boolean isRunScheduling() {
        return runScheduling;
    }

    /**
     * 
     * @see nl.strohalm.cyclos.utils.MessageResourcesLoadedListener#onApplicationResourcesLoaded()
     */
    @Override
    public void onApplicationResourcesLoaded() {
        Iterator<Alerts> it = deferredEvents.keySet().iterator();
        while (it.hasNext()) {
            Alerts key = it.next();
            String val = deferredEvents.get(key);

            alertService.create(key, val);
        }
    }

    @Override
    public void purgeIndexOperations(final Calendar time) {
        Calendar limit = (Calendar) time.clone();
        limit.add(Calendar.HOUR_OF_DAY, -24);
        indexOperationDao.deleteBefore(limit);
    }

    @Override
    public void rebuildIndexes(final Class<? extends Indexable> entityType) {
        for (Class<? extends Indexable> entityClass : resolveIndexedClasses(entityType)) {
            indexHandler.rebuild(entityClass);
        }
    }

    public void setAccessServiceLocal(final AccessServiceLocal accessService) {
        this.accessService = accessService;
    }

    public void setAlertServiceLocal(final AlertServiceLocal alertService) {
        this.alertService = alertService;
    }

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void setApplicationDao(final ApplicationDAO applicationDao) {
        this.applicationDao = applicationDao;
    }

    public void setCacheManager(final CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    public void setCyclosProperties(final Properties cyclosProperties) {
        this.cyclosProperties = cyclosProperties;
    }

    public void setErrorLogServiceLocal(final ErrorLogServiceLocal errorLogService) {
        this.errorLogService = errorLogService;
    }

    public void setIndexHandler(final IndexHandler indexHandler) {
        this.indexHandler = indexHandler;
    }

    public void setIndexOperationDao(final IndexOperationDAO indexOperationDao) {
        this.indexOperationDao = indexOperationDao;
    }

    public void setInstanceHandler(final InstanceHandler instanceHandler) {
        this.instanceHandler = instanceHandler;
    }

    public void setInvoiceServiceLocal(final InvoiceServiceLocal invoiceService) {
        this.invoiceService = invoiceService;
    }

    public void setMessageResolver(final MessageResolver messageResolver) {
        this.messageResolver = messageResolver;
    }

    public void setMessageServiceLocal(final MessageServiceLocal messageService) {
        this.messageService = messageService;
    }

    @Override
    public void setOnline(final boolean online) {
        Application application = getApplication();
        final boolean changed = application.isOnline() != online;
        if (changed) {
            if (online && (rateService.checkPendingRateInitializations(null) != null)) {
                throw new ValidationException("rates.error.notOnlineWhileRateInitsPending");
            }
            application.setOnline(online);
            applicationDao.update(application);
            getCache().remove(APPLICATION_CACHE_KEY);

            if (!online) {
                // Disconnect all logged users but the current user
                accessService.disconnectAllButLogged();
            }
        }
    }

    public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) {
        this.permissionService = permissionService;
    }

    public void setPollingTasksHandler(final PollingTasksHandler pollingTasksHandler) {
        this.pollingTasksHandler = pollingTasksHandler;
    }

    public void setRateServiceLocal(final RateServiceLocal rateService) {
        this.rateService = rateService;
    }

    public void setSchedulingHandler(final SchedulingHandler schedulingHandler) {
        this.schedulingHandler = schedulingHandler;
    }

    public void setTaskRunner(final TaskRunner taskRunner) {
        this.taskRunner = taskRunner;
    }

    public void setTransactionHelper(final TransactionHelper transactionHelper) {
        this.transactionHelper = transactionHelper;
    }

    @Override
    public void shutdown() {
        initialized = false;
        transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(final TransactionStatus status) {
                alertService.create(SystemAlert.Alerts.APPLICATION_SHUTDOWN, instanceHandler.getId());
            }
        });
        applicationDao.shutdownDBIfNeeded();
    }

    private int countOpenInvoices() {
        final InvoiceQuery query = new InvoiceQuery();
        query.setOwner(SystemAccountOwner.instance());
        query.setDirection(InvoiceQuery.Direction.INCOMING);
        query.setStatus(Invoice.Status.OPEN);
        query.setPageForCount();
        return PageHelper.getTotalCount(invoiceService.search(query));
    }

    private int countUnreadMessages() {
        final MessageQuery query = new MessageQuery();
        query.setGetter(LoggedUser.element());
        query.setMessageBox(MessageBox.INBOX);
        query.setRead(false);
        query.setPageForCount();
        return PageHelper.getTotalCount(messageService.search(query));
    }

    /**
     * Defer the event creation for later firing
     * 
     * @param applicationRestarted Alert type
     * @param id application id
     */
    private void deferEvent(final Alerts applicationRestarted, final String id) {
        deferredEvents.put(applicationRestarted, id);
    }

    private Application getApplication() {
        return getCache().get(APPLICATION_CACHE_KEY, new CacheCallback() {
            @Override
            public Object retrieve() {
                return applicationDao.read();
            }
        });
    }

    private Cache getCache() {
        return cacheManager.getCache("cyclos.Application");
    }

    /**
     * Returns all indexed classes if the parameter is null, or a singleton collection otherwise
     */
    private Collection<Class<? extends Indexable>> resolveIndexedClasses(
            final Class<? extends Indexable> entityType) {
        if (entityType == null) {
            Collection<Class<? extends Indexable>> entityClasses = new ArrayList<Class<? extends Indexable>>();
            for (EntityType type : EntityType.values()) {
                entityClasses.add(type.getEntityClass());
            }
            return entityClasses;
        } else {
            return Collections.<Class<? extends Indexable>>singleton(entityType);
        }
    }

    /**
     * Run all given local initializations in its own transaction
     */
    private void runAll(final Collection<LocalInitialization> initializations) {
        for (final LocalInitialization initialization : initializations) {
            LOG.debug(String.format("Running initialization (%s)...", initialization.getName()));
            transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(final TransactionStatus status) {
                    initialization.initialize();
                }
            });
        }
    }

    private void setHeadlessMode() {
        System.setProperty("java.awt.headless", "true");
    }
}