PollDomainModelBean.java

/*
Developed with the contribution of the European Commission - Directorate General for Maritime Affairs and Fisheries
© European Union, 2015-2016.

This file is part of the Integrated Fisheries Data Management (IFDM) Suite. The IFDM Suite 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 3 of the License, or any later version. The IFDM Suite 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 the IFDM Suite. If not, see <http://www.gnu.org/licenses/>.
 */
package eu.europa.ec.fisheries.uvms.mobileterminal.bean;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.ejb.EJB;
import javax.ejb.Stateless;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollId;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollListQuery;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollListResponse;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollMobileTerminal;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollRequestType;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollResponseType;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollStatus;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollType;
import eu.europa.ec.fisheries.schema.mobileterminal.polltypes.v1.PollableQuery;
import eu.europa.ec.fisheries.schema.mobileterminal.types.v1.MobileTerminalType;
import eu.europa.ec.fisheries.schema.mobileterminal.types.v1.PluginCapabilityType;
import eu.europa.ec.fisheries.uvms.mobileterminal.PollDomainModel;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.ChannelDao;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.PollDao;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.PollProgramDao;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.TerminalDao;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.exception.EnumException;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.exception.InputArgumentException;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.exception.NoEntityFoundException;
import eu.europa.ec.fisheries.uvms.mobileterminal.dao.exception.PollDaoException;
import eu.europa.ec.fisheries.uvms.mobileterminal.dto.ListResponseDto;
import eu.europa.ec.fisheries.uvms.mobileterminal.entity.Channel;
import eu.europa.ec.fisheries.uvms.mobileterminal.entity.MobileTerminal;
import eu.europa.ec.fisheries.uvms.mobileterminal.entity.MobileTerminalPluginCapability;
import eu.europa.ec.fisheries.uvms.mobileterminal.entity.poll.Poll;
import eu.europa.ec.fisheries.uvms.mobileterminal.entity.poll.PollProgram;
import eu.europa.ec.fisheries.uvms.mobileterminal.entity.types.MobileTerminalTypeEnum;
import eu.europa.ec.fisheries.uvms.mobileterminal.mapper.EnumMapper;
import eu.europa.ec.fisheries.uvms.mobileterminal.mapper.MobileTerminalEntityToModelMapper;
import eu.europa.ec.fisheries.uvms.mobileterminal.mapper.PollEntityToModelMapper;
import eu.europa.ec.fisheries.uvms.mobileterminal.mapper.PollModelToEntityMapper;
import eu.europa.ec.fisheries.uvms.mobileterminal.model.exception.MobileTerminalModelException;
import eu.europa.ec.fisheries.uvms.mobileterminal.model.exception.MobileTerminalModelMapperException;
import eu.europa.ec.fisheries.uvms.mobileterminal.search.PollSearchKeyValue;
import eu.europa.ec.fisheries.uvms.mobileterminal.search.poll.PollSearchMapper;

/**
 **/
@Stateless
public class PollDomainModelBean implements PollDomainModel {
    final static Logger LOG = LoggerFactory.getLogger(PollDomainModelBean.class);

    @EJB
    PollDao pollDao;

    @EJB
    PollProgramDao pollProgramDao;

    @EJB
    TerminalDao terminalDao;

    @EJB
    ChannelDao channelDao;
    
    private MobileTerminalType mapPollableTerminalType(MobileTerminalTypeEnum type, String guid) throws MobileTerminalModelException {
        MobileTerminal terminal = terminalDao.getMobileTerminalByGuid(guid);
        return MobileTerminalEntityToModelMapper.mapToMobileTerminalType(terminal);
	}

    private MobileTerminalType getPollableTerminalType(String guid, String channelGuid) throws MobileTerminalModelException {
        MobileTerminal terminal = terminalDao.getMobileTerminalByGuid(guid);
        checkPollable(terminal);

        if(channelGuid != null && !channelGuid.isEmpty()) {
            for (Channel channel : terminal.getChannels()) {
                if (channel.getGuid().equalsIgnoreCase(channelGuid)) {
                    if (!channel.getMobileTerminal().getGuid().equalsIgnoreCase(guid)) {
                        throw new MobileTerminalModelException("Channel " + channel.getGuid() + " can not be polled, because it is not part of terminal " + terminal.getGuid());
                    }
                    return MobileTerminalEntityToModelMapper.mapToMobileTerminalType(terminal, channel);
                }
            }

        }
		throw new MobileTerminalModelException("Could not find channel " + channelGuid + " based on");
    }
    
    private void checkPollable(MobileTerminal terminal) throws MobileTerminalModelException {
    	if(terminal.getArchived()) {
    		throw new MobileTerminalModelException("Terminal is archived");
    	}
    	if(terminal.getInactivated()) {
    		throw new MobileTerminalModelException("Terminal is inactive");
    	}
    	if(terminal.getPlugin() != null && terminal.getPlugin().getPluginInactive()) {
    		throw new MobileTerminalModelException("Terminal connected to no longer active Plugin (LES)");
    	}
    }
    
    @Override
    public List<PollResponseType> createPolls(PollRequestType pollRequest, String username) throws MobileTerminalModelException {
        LOG.info("Create polls.");

        if (pollRequest == null || pollRequest.getPollType() == null) {
            throw new InputArgumentException("No polls to create");
        }

        if (pollRequest.getComment() == null || pollRequest.getUserName() == null) {
            throw new InputArgumentException("Cannot create without comment and user");
        }
        
        if(pollRequest.getMobileTerminals() == null) {
        	throw new MobileTerminalModelException("No mobile terminals for " + pollRequest.getPollType());
        }

        List<PollResponseType> responseList;
        Map<Poll, MobileTerminalType> pollMobileTerminalTypeMap;
        switch (pollRequest.getPollType()) {
            case PROGRAM_POLL:
                Map<PollProgram, MobileTerminalType> pollProgramMobileTerminalTypeMap = validateAndMapToProgramPolls(pollRequest, username);
                responseList = createPollPrograms(pollProgramMobileTerminalTypeMap, username);
                break;
            case CONFIGURATION_POLL:
            case MANUAL_POLL:
            case SAMPLING_POLL:
                pollMobileTerminalTypeMap = validateAndMapToPolls(pollRequest, username);
                responseList = createPolls(pollMobileTerminalTypeMap, pollRequest.getPollType());
                break;
            default:
                LOG.error("[ Could not decide poll type ] {}", pollRequest.getPollType());
                throw new MobileTerminalModelException("Could not decide Poll Type when creating polls");
        }
        return responseList;
    }

    private Map<PollProgram, MobileTerminalType> validateAndMapToProgramPolls(PollRequestType pollRequest, String username) throws MobileTerminalModelException {
        Map<PollProgram, MobileTerminalType> map = new HashMap<>();

        for(PollMobileTerminal pollTerminal : pollRequest.getMobileTerminals()) {
            MobileTerminal mobileTerminalEntity = terminalDao.getMobileTerminalByGuid(pollTerminal.getMobileTerminalId());
            String connectId = mobileTerminalEntity.getCurrentEvent().getConnectId();
            if (!pollTerminal.getConnectId().equals(connectId)) {
                throw new MobileTerminalModelException("Terminal " + mobileTerminalEntity.getGuid() + " can not be polled, because it is not linked to asset " + connectId);
            }
            MobileTerminalType terminalType = getPollableTerminalType(pollTerminal.getMobileTerminalId(), pollTerminal.getComChannelId());
            PollProgram pollProgram = PollModelToEntityMapper.mapToProgramPoll(mobileTerminalEntity, connectId, pollTerminal.getComChannelId(), pollRequest, username);
            map.put(pollProgram, terminalType);
        }
        return map;
    }

    private Map<Poll, MobileTerminalType> validateAndMapToPolls(PollRequestType pollRequest, String username) throws MobileTerminalModelException {
        Map<Poll, MobileTerminalType> map = new HashMap<>();

        for(PollMobileTerminal pollTerminal : pollRequest.getMobileTerminals()) {
            MobileTerminal mobileTerminalEntity = terminalDao.getMobileTerminalByGuid(pollTerminal.getMobileTerminalId());
            String connectId = mobileTerminalEntity.getCurrentEvent().getConnectId();
            if (!pollTerminal.getConnectId().equals(connectId)) {
                throw new MobileTerminalModelException("Terminal " + mobileTerminalEntity.getGuid() + " can not be polled, because it is not linked to asset " + connectId);
            }

            if(pollRequest.getPollType() != PollType.MANUAL_POLL){
                validateMobileTerminalPluginCapability(mobileTerminalEntity.getPlugin().getCapabilities(), pollRequest.getPollType(), mobileTerminalEntity.getPlugin().getPluginServiceName());
            }
            MobileTerminalType terminalType = getPollableTerminalType(pollTerminal.getMobileTerminalId(), pollTerminal.getComChannelId());
            Poll poll = PollModelToEntityMapper.mapToPoll(mobileTerminalEntity, connectId, pollTerminal.getComChannelId(), pollRequest, username);
            map.put(poll, terminalType);
        }
        return map;
    }

    private void validateMobileTerminalPluginCapability(Set<MobileTerminalPluginCapability> capabilities, PollType pollType, String pluginServiceName) throws MobileTerminalModelException {
        PluginCapabilityType pluginCapabilityType = PluginCapabilityType.CONFIGURABLE;
        switch (pollType){
            case CONFIGURATION_POLL:
                pluginCapabilityType = PluginCapabilityType.CONFIGURABLE;
                break;
            case SAMPLING_POLL:
                pluginCapabilityType = PluginCapabilityType.SAMPLING;
                break;
            default:
                throw new MobileTerminalModelException("Cannot create " + pollType.name() +"  poll when plugin: " + pluginServiceName);
        }
        if (!validatePluginHasCapabilityConfigurable(capabilities, pluginCapabilityType)){
            throw new MobileTerminalModelException("Cannot create " + pollType.name() +"  poll when plugin: " + pluginServiceName + " has not capability " + pluginCapabilityType.name()+" set");
        }
    }

    private boolean validatePluginHasCapabilityConfigurable(Set<MobileTerminalPluginCapability> capabilities, PluginCapabilityType pluginCapability) {
        for (MobileTerminalPluginCapability pluginCap : capabilities){
            if(pluginCapability.name().equalsIgnoreCase(pluginCap.getName())){
                return true;
            }
        }
        return false;
    }

    private List<PollResponseType> createPollPrograms(Map<PollProgram, MobileTerminalType> map, String username) throws MobileTerminalModelException {
        List<PollResponseType> responseList = new ArrayList<>();
        Iterator<Map.Entry<PollProgram, MobileTerminalType>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<PollProgram, MobileTerminalType> next = iterator.next();
            PollProgram pollProgram = next.getKey();
            MobileTerminalType mobileTerminalType = next.getValue();
            try {
                pollDao.createPollProgram(pollProgram);
                responseList.add(PollEntityToModelMapper.mapToPollResponseType(pollProgram, mobileTerminalType));
            } catch (PollDaoException e) {
                LOG.error("[ Could not persist PollProgram ]");
                throw new MobileTerminalModelException("Could not persist PollProgam");
            } catch (MobileTerminalModelMapperException e) {
                LOG.error("Could not map PollProgram to PollResponseType");
                throw e;
            }
        }
        return responseList;
    }

    private List<PollResponseType> createPolls(Map<Poll, MobileTerminalType> map, PollType pollType) throws MobileTerminalModelException {
        List<PollResponseType> responseList = new ArrayList<>();
        Iterator<Map.Entry<Poll, MobileTerminalType>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Poll, MobileTerminalType> next = iterator.next();
            Poll poll = next.getKey();
            MobileTerminalType mobileTerminalType = next.getValue();
            try {
                pollDao.createPoll(poll);
                responseList.add(PollEntityToModelMapper.mapToPollResponseType(poll, mobileTerminalType, pollType));
            } catch (PollDaoException e) {
                LOG.error("[ Could not persist Poll ]");
                throw new MobileTerminalModelException("Could not persist Poll");
            } catch (MobileTerminalModelMapperException e) {
                LOG.error("Could not map Poll to PollResponseType");
                throw e;
            }
        }
        return responseList;

    }

    @Override
    public PollListResponse getPollList(PollListQuery query) throws MobileTerminalModelException {
        LOG.info("Get poll list from query");

        if (query == null) {
        	throw new InputArgumentException("Cannot get poll list because no query.");
        }

        if (query.getPagination() == null) {
        	throw new InputArgumentException("Cannot get poll list because no list pagination.");
        }

        if(query.getPollSearchCriteria() == null || query.getPollSearchCriteria().getCriterias() == null) {
        	throw new InputArgumentException("Cannot get poll list because criteria are null.");
        }
        PollListResponse response = new PollListResponse();
        List<PollResponseType> pollResponseList = new ArrayList<>();

        Integer page = query.getPagination().getPage();
        Integer listSize = query.getPagination().getListSize();
        boolean isDynamic = query.getPollSearchCriteria().isIsDynamic();
        List<PollSearchKeyValue> searchKeys = PollSearchMapper.createSearchFields(query.getPollSearchCriteria().getCriterias());
        
        String countSql = PollSearchMapper.createCountSearchSql(searchKeys, isDynamic);
        String sql = PollSearchMapper.createSelectSearchSql(searchKeys, isDynamic);

        Long numberMatches = pollDao.getPollListSearchCount(countSql, searchKeys, isDynamic);
        List<Poll> pollList = pollDao.getPollListSearchPaginated(page, listSize, sql, searchKeys, isDynamic);
        
        for (Poll poll : pollList) {
        	try {
        		MobileTerminal mobileTerminalEntity = poll.getPollBase().getMobileTerminal();
        		MobileTerminalType mobileTerminalType = mapPollableTerminalType(mobileTerminalEntity.getMobileTerminalType(), mobileTerminalEntity.getGuid());
        		PollResponseType pollType = PollEntityToModelMapper.mapToPollResponseType(poll, mobileTerminalType, EnumMapper.getPollModelFromType(poll.getPollType()));
        		pollResponseList.add(pollType);
        	} catch (EnumException e) {
        		LOG.error("[ Poll " + poll.getGuid() + "  couldn't map type ]");
        	}
        }

        int numberOfPages = (int) (numberMatches / listSize);
        if (numberMatches % listSize != 0) {
            numberOfPages += 1;
        }

        response.setTotalNumberOfPages(numberOfPages);
        response.setCurrentPage(query.getPagination().getPage());
        response.getPollList().addAll(pollResponseList);
        return response;
    }
    
    @Override
    public List<PollResponseType> getPollProgramList(boolean onlyAlivePrograms) throws MobileTerminalModelException {
        LOG.info("Get poll program list");
        List<PollProgram> pollPrograms = pollProgramDao.getProgramPollsAlive();
        
        List<PollResponseType> responseList = new ArrayList<>();
        for(PollProgram pollProgram : pollPrograms) {
        	try {
        		MobileTerminal terminal = pollProgram.getPollBase().getMobileTerminal();
        		MobileTerminalType terminalType = mapPollableTerminalType(terminal.getMobileTerminalType(), terminal.getGuid());
        		responseList.add(PollEntityToModelMapper.mapToPollResponseType(pollProgram, terminalType));
        	} catch (NoEntityFoundException e) {
        		LOG.error("[ Unvalid mobile terminal connected to poll program " + pollProgram.getGuid() + " ]");
        	    throw new MobileTerminalModelException("[ Unvalid mobile terminal connected to poll program " + pollProgram.getGuid() + " ]");
        	}
        }
        return responseList;
    }

    @Override
    public List<PollResponseType> getPollProgramRunningAndStarted() throws MobileTerminalModelException {
        LOG.info("Get running and started poll program list");
        List<PollProgram> pollPrograms = pollProgramDao.getPollProgramRunningAndStarted();

        List<PollResponseType> responseList = new ArrayList<>();
        for(PollProgram pollProgram : pollPrograms) {
        	try {
        		MobileTerminal terminal = pollProgram.getPollBase().getMobileTerminal();
        		MobileTerminalType terminalType = mapPollableTerminalType(terminal.getMobileTerminalType(), terminal.getGuid());
        		responseList.add(PollEntityToModelMapper.mapToPollResponseType(pollProgram, terminalType));
        	} catch (NoEntityFoundException e) {
        		LOG.error("[ Unvalid mobile terminal connected to poll program " + pollProgram.getGuid() + " ]");
        	    throw new MobileTerminalModelException("[ Unvalid mobile terminal connected to poll program " + pollProgram.getGuid() + " ]");
        	}
        }
        return responseList;
    }

    @Override
    public PollResponseType setStatusPollProgram(PollId id, PollStatus state) throws MobileTerminalModelException {
        LOG.info("Set status of poll");

        if (id == null || id.getGuid() == null || id.getGuid().isEmpty()) {
            throw new InputArgumentException("No poll id given");
        }
        if (state == null) {
            throw new InputArgumentException("No status to set");
        }
        
        try {
            PollProgram program = pollProgramDao.getPollProgramByGuid(id.getGuid());
            MobileTerminal terminal = program.getPollBase().getMobileTerminal();
            MobileTerminalType terminalType = mapPollableTerminalType(terminal.getMobileTerminalType(), terminal.getGuid());
            
            switch (program.getPollState()) {
            case ARCHIVED:
                throw new MobileTerminalModelException("Can not change status of archived program poll, id: [ " + id.getGuid() + " ]");
            case STARTED:
            case STOPPED:
            }

            // TODO
            // check terminal/comchannel?

            program.setPollState(EnumMapper.getPollStateTypeFromModel(state));

            return PollEntityToModelMapper.mapToPollResponseType(program, terminalType);
        } catch (EnumException e) {
            LOG.error("[ Error when setting poll program status. ] {}", e.getMessage());
            throw new MobileTerminalModelException(e.getMessage());
        } catch (NoEntityFoundException e) {
        	LOG.error("[ Unvalid mobile terminal connected to poll program ]");
        	throw new MobileTerminalModelException("[ Unvalid mobile terminal connected to poll program ]");
        }
    }

    @Override
    public ListResponseDto getMobileTerminalPollableList(PollableQuery query) throws MobileTerminalModelException {
        LOG.info("Get list of pollable InmarsatC terminals.");

        if (query == null) {
            throw new InputArgumentException("No query");
        }

        if (query.getPagination() == null) {
            throw new InputArgumentException("No list pagination");
        }

        ListResponseDto response = new ListResponseDto();
        List<MobileTerminalType> mobileTerminalList = new ArrayList<MobileTerminalType>();

        Integer page = query.getPagination().getPage();
        Integer listSize = query.getPagination().getListSize();
        int startIndex = (page-1)*listSize;
        int stopIndex = startIndex+listSize;
        LOG.debug("page: " + page + ", listSize: " + listSize + ", startIndex: " + startIndex);
        
        List<String> idList = query.getConnectIdList();
        long in = System.currentTimeMillis();

        //Long numberMatches = channelInmarsatCDao.getPollableListSearchCount(countSql, idList);
        List<Channel> channels = channelDao.getPollableListSearch(idList);
        
        for (Channel comchannel : channels) {
        	//TODO slim response from Pollable
        	MobileTerminalType terminal = MobileTerminalEntityToModelMapper.mapToMobileTerminalType(comchannel.getMobileTerminal(), comchannel);
            mobileTerminalList.add(terminal);
        }
        
        int numberMatches = mobileTerminalList.size();
        
        int numberOfPages = (int) (numberMatches / listSize);
        if (numberMatches % listSize != 0) {
            numberOfPages += 1;
        }

        if((numberMatches-1) <= 0) {
        	response.setMobileTerminalList(mobileTerminalList);
        } else {
        	if(stopIndex >= numberMatches) {
            	stopIndex = numberMatches;
            }
        	LOG.debug("stopIndex: " + stopIndex);
            List<MobileTerminalType> newList = new ArrayList<>(mobileTerminalList.subList(startIndex, stopIndex));
        	response.setMobileTerminalList(newList);
        }
        
        
        response.setTotalNumberOfPages(numberOfPages);
        response.setCurrentPage(query.getPagination().getPage());
        
        long out = System.currentTimeMillis();
        LOG.debug("Get pollable channels " + (out-in) + " ms");
        return response;

    }

}