org.ejbca.core.ejb.approval.ApprovalSessionBean.java Source code

Java tutorial

Introduction

Here is the source code for org.ejbca.core.ejb.approval.ApprovalSessionBean.java

Source

/*************************************************************************
 *                                                                       *
 *  EJBCA Community: The OpenSource Certificate Authority                *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/

package org.ejbca.core.ejb.approval;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.cesecore.ErrorCode;
import org.cesecore.audit.enums.EventStatus;
import org.cesecore.audit.log.SecurityEventsLoggerSessionLocal;
import org.cesecore.authentication.tokens.AuthenticationToken;
import org.cesecore.authentication.tokens.X509CertificateAuthenticationToken;
import org.cesecore.authorization.AuthorizationDeniedException;
import org.cesecore.authorization.control.AccessControlSessionLocal;
import org.cesecore.authorization.control.StandardRules;
import org.cesecore.certificates.certificate.CertificateStoreSessionLocal;
import org.cesecore.certificates.endentity.EndEntityInformation;
import org.cesecore.configuration.GlobalConfigurationSessionLocal;
import org.cesecore.jndi.JndiConstants;
import org.cesecore.util.Base64;
import org.cesecore.util.CertTools;
import org.cesecore.util.ProfileID;
import org.ejbca.config.GlobalConfiguration;
import org.ejbca.core.ejb.audit.enums.EjbcaEventTypes;
import org.ejbca.core.ejb.audit.enums.EjbcaModuleTypes;
import org.ejbca.core.ejb.audit.enums.EjbcaServiceTypes;
import org.ejbca.core.ejb.ra.EndEntityAccessSessionLocal;
import org.ejbca.core.model.InternalEjbcaResources;
import org.ejbca.core.model.approval.AdminAlreadyApprovedRequestException;
import org.ejbca.core.model.approval.Approval;
import org.ejbca.core.model.approval.ApprovalDataUtil;
import org.ejbca.core.model.approval.ApprovalDataVO;
import org.ejbca.core.model.approval.ApprovalException;
import org.ejbca.core.model.approval.ApprovalNotificationParamGen;
import org.ejbca.core.model.approval.ApprovalRequest;
import org.ejbca.core.model.approval.ApprovalRequestExpiredException;
import org.ejbca.core.model.authorization.AccessRulesConstants;
import org.ejbca.util.mail.MailSender;
import org.ejbca.util.query.IllegalQueryException;
import org.ejbca.util.query.Query;

/**
 * Keeps track of approval requests and their approval or rejects.
 * 
 * @version $Id: ApprovalSessionBean.java 20582 2015-01-20 16:21:19Z mikekushner $
 */
@Stateless(mappedName = JndiConstants.APP_JNDI_PREFIX + "ApprovalSessionRemote")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ApprovalSessionBean implements ApprovalSessionLocal, ApprovalSessionRemote {

    private static final Logger log = Logger.getLogger(ApprovalSessionBean.class);

    /** Internal localization of logs and errors */
    private static final InternalEjbcaResources intres = InternalEjbcaResources.getInstance();

    @PersistenceContext(unitName = "ejbca")
    private EntityManager entityManager;

    @EJB
    private AccessControlSessionLocal authorizationSession;
    @EJB
    private CertificateStoreSessionLocal certificateStoreSession;
    @EJB
    private SecurityEventsLoggerSessionLocal auditSession;
    @EJB
    private EndEntityAccessSessionLocal endEntityAccessSession;
    @EJB
    private GlobalConfigurationSessionLocal globalConfigurationSession;

    @Override
    public void addApprovalRequest(AuthenticationToken admin, ApprovalRequest approvalRequest)
            throws ApprovalException {
        if (log.isTraceEnabled()) {
            log.trace(">addApprovalRequest");
        }
        int approvalId = approvalRequest.generateApprovalId();

        ApprovalDataVO data = findNonExpiredApprovalRequest(admin, approvalId);
        if (data != null) {
            String msg = intres.getLocalizedMessage("approval.alreadyexists", approvalId);
            log.info(msg);
            throw new ApprovalException(ErrorCode.APPROVAL_ALREADY_EXISTS, msg);
        } else {
            // There exists no approval request with status waiting. Add a new one
            try {
                final Integer freeId = findFreeApprovalId();
                final ApprovalData approvalData = new ApprovalData(freeId);
                approvalData.setApprovalid(approvalRequest.generateApprovalId());
                approvalData.setApprovaltype(approvalRequest.getApprovalType());
                approvalData.setEndentityprofileid(approvalRequest.getEndEntityProfileId());
                approvalData.setCaid(approvalRequest.getCAId());
                if (approvalRequest.getRequestAdminCert() != null) {
                    approvalData
                            .setReqadmincertissuerdn(CertTools.getIssuerDN(approvalRequest.getRequestAdminCert()));
                    approvalData.setReqadmincertsn(
                            CertTools.getSerialNumberAsString(approvalRequest.getRequestAdminCert()));
                }
                setApprovalRequest(approvalData, approvalRequest);
                setApprovals(approvalData, new ArrayList<Approval>());
                approvalData.setExpiredate((new Date()).getTime() + approvalRequest.getRequestValidity());
                approvalData.setRemainingapprovals(approvalRequest.getNumOfRequiredApprovals());
                entityManager.persist(approvalData);
                final GlobalConfiguration gc = (GlobalConfiguration) globalConfigurationSession
                        .getCachedConfiguration(GlobalConfiguration.GLOBAL_CONFIGURATION_ID);
                if (gc.getUseApprovalNotifications()) {
                    sendApprovalNotification(admin, gc.getApprovalAdminEmailAddress(),
                            gc.getApprovalNotificationFromAddress(),
                            gc.getBaseUrl() + "adminweb/approval/approveaction.jsf?uniqueId=" + freeId,
                            intres.getLocalizedMessage("notification.newrequest.subject"),
                            intres.getLocalizedMessage("notification.newrequest.msg"), freeId,
                            approvalRequest.getNumOfRequiredApprovals(), new Date(), approvalRequest, null);
                }
                String msg = intres.getLocalizedMessage("approval.addedwaiting", approvalId);
                final Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                auditSession.log(EjbcaEventTypes.APPROVAL_ADD, EventStatus.SUCCESS, EjbcaModuleTypes.APPROVAL,
                        EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(approvalRequest.getCAId()), null,
                        null, details);
            } catch (Exception e1) {
                String msg = intres.getLocalizedMessage("approval.erroradding", approvalId);
                log.error(msg, e1);
                final Map<String, Object> details = new LinkedHashMap<String, Object>();
                details.put("msg", msg);
                details.put("Error", e1.getMessage());
                auditSession.log(EjbcaEventTypes.APPROVAL_ADD, EventStatus.FAILURE, EjbcaModuleTypes.APPROVAL,
                        EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(approvalRequest.getCAId()), null,
                        null, details);
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<addApprovalRequest");
        }
    }

    @Override
    public void removeApprovalRequest(AuthenticationToken admin, int id) throws ApprovalException {
        log.trace(">removeApprovalRequest");
        try {
            ApprovalData ad = ApprovalData.findById(entityManager, Integer.valueOf(id));
            if (ad != null) {
                entityManager.remove(ad);
                final String detailsMsg = intres.getLocalizedMessage("approval.removed", id);
                auditSession.log(EjbcaEventTypes.APPROVAL_REMOVE, EventStatus.SUCCESS, EjbcaModuleTypes.APPROVAL,
                        EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(ad.getCaid()), null, null,
                        detailsMsg);
            } else {
                String msg = intres.getLocalizedMessage("approval.notexist", id);
                log.info(msg);
                throw new ApprovalException(ErrorCode.APPROVAL_REQUEST_ID_NOT_EXIST, msg);
            }
        } catch (Exception e) {
            String msg = intres.getLocalizedMessage("approval.errorremove", id);
            final Map<String, Object> details = new LinkedHashMap<String, Object>();
            details.put("msg", msg);
            details.put("error", e.getMessage());
            auditSession.log(EjbcaEventTypes.APPROVAL_REMOVE, EventStatus.FAILURE, EjbcaModuleTypes.APPROVAL,
                    EjbcaServiceTypes.EJBCA, admin.toString(), null, null, null, details);
            log.error("Error removing approval request", e);
        }
        log.trace("<removeApprovalRequest");
    }

    @Override
    public void reject(AuthenticationToken admin, int approvalId, Approval approval)
            throws ApprovalRequestExpiredException, AuthorizationDeniedException, ApprovalException,
            AdminAlreadyApprovedRequestException {
        log.trace(">reject");
        ApprovalData adl;
        try {
            adl = isAuthorizedBeforeApproveOrReject(admin, approvalId);
        } catch (ApprovalException e1) {
            String msg = intres.getLocalizedMessage("approval.notexist", approvalId);
            log.info(msg);
            throw e1;
        }

        checkExecutionPossibility(admin, adl);
        approval.setApprovalAdmin(false, admin);

        try {
            reject(adl, approval);
            final GlobalConfiguration gc = (GlobalConfiguration) globalConfigurationSession
                    .getCachedConfiguration(GlobalConfiguration.GLOBAL_CONFIGURATION_ID);
            if (gc.getUseApprovalNotifications()) {
                final ApprovalDataVO approvalDataVO = getApprovalDataVO(adl);
                sendApprovalNotification(admin, gc.getApprovalAdminEmailAddress(),
                        gc.getApprovalNotificationFromAddress(),
                        gc.getBaseUrl() + "adminweb/approval/approveaction.jsf?uniqueId=" + adl.getId(),
                        intres.getLocalizedMessage("notification.requestrejected.subject"),
                        intres.getLocalizedMessage("notification.requestrejected.msg"), adl.getId(),
                        approvalDataVO.getRemainingApprovals(), approvalDataVO.getRequestDate(),
                        approvalDataVO.getApprovalRequest(), approval);
            }
            final String detailsMsg = intres.getLocalizedMessage("approval.rejected", approvalId);
            auditSession.log(EjbcaEventTypes.APPROVAL_REJECT, EventStatus.SUCCESS, EjbcaModuleTypes.APPROVAL,
                    EjbcaServiceTypes.EJBCA, admin.toString(), String.valueOf(adl.getCaid()), null, null,
                    detailsMsg);
        } catch (ApprovalRequestExpiredException e) {
            String msg = intres.getLocalizedMessage("approval.expired", approvalId);
            log.info(msg);
            throw e;
        }
        log.trace("<reject");
    }

    @Override
    public void checkExecutionPossibility(AuthenticationToken admin, ApprovalData adl)
            throws AdminAlreadyApprovedRequestException {
        // Check that the approver's principals don't exist among the existing usernames.
        ApprovalDataVO data = getApprovalDataVO(adl);
        int approvalId = data.getApprovalId();
        // String username = admin.getUsername();
        if (data.getReqadmincertissuerdn() != null) {
            // Check that the approver isn't the same as requested the action.
            AuthenticationToken requester = getRequestAuthenticationToken(adl);
            if (admin.equals(requester)) {
                String msg = intres.getLocalizedMessage("approval.alreadyapproved", approvalId);
                log.info(msg);
                throw new AdminAlreadyApprovedRequestException(msg);
            }
        }
        // Check that his admin has not approved this this request before
        for (Approval next : data.getApprovals()) {
            if (next.getAdmin().equals(admin)) {
                String msg = intres.getLocalizedMessage("approval.alreadyapproved", approvalId);
                log.info(msg);
                throw new AdminAlreadyApprovedRequestException(msg);
            }
        }
    }

    @Override
    public ApprovalData isAuthorizedBeforeApproveOrReject(AuthenticationToken admin, int approvalId)
            throws ApprovalException, AuthorizationDeniedException {
        ApprovalData retval = findNonExpiredApprovalDataLocal(approvalId);
        if (retval != null) {
            if (retval.getEndentityprofileid() == ApprovalDataVO.ANY_ENDENTITYPROFILE) {
                if (!authorizationSession.isAuthorized(admin, AccessRulesConstants.REGULAR_APPROVECAACTION)) {
                    final String msg = intres.getLocalizedMessage("authorization.notuathorizedtoresource",
                            AccessRulesConstants.REGULAR_APPROVECAACTION, null);
                    throw new AuthorizationDeniedException(msg);
                }
            } else {
                if (!authorizationSession.isAuthorized(admin, AccessRulesConstants.REGULAR_APPROVEENDENTITY)) {
                    final String msg = intres.getLocalizedMessage("authorization.notuathorizedtoresource",
                            AccessRulesConstants.REGULAR_APPROVEENDENTITY, null);
                    throw new AuthorizationDeniedException(msg);
                }
                GlobalConfiguration globalConfiguration = (GlobalConfiguration) globalConfigurationSession
                        .getCachedConfiguration(GlobalConfiguration.GLOBAL_CONFIGURATION_ID);
                if (globalConfiguration.getEnableEndEntityProfileLimitations()) {
                    if (!authorizationSession.isAuthorized(admin, AccessRulesConstants.ENDENTITYPROFILEPREFIX
                            + retval.getEndentityprofileid() + AccessRulesConstants.APPROVE_END_ENTITY)) {
                        final String msg = intres.getLocalizedMessage(
                                "authorization.notuathorizedtoresource", AccessRulesConstants.ENDENTITYPROFILEPREFIX
                                        + retval.getEndentityprofileid() + AccessRulesConstants.APPROVE_END_ENTITY,
                                null);
                        throw new AuthorizationDeniedException(msg);
                    }
                }
            }
            if (retval.getCaid() != ApprovalDataVO.ANY_CA) {
                if (!authorizationSession.isAuthorized(admin,
                        StandardRules.CAACCESS.resource() + retval.getCaid())) {
                    final String msg = intres.getLocalizedMessage("authorization.notuathorizedtoresource",
                            StandardRules.CAACCESS.resource() + retval.getCaid(), null);
                    throw new AuthorizationDeniedException(msg);
                }
            }
        } else {
            throw new ApprovalException(ErrorCode.APPROVAL_REQUEST_ID_NOT_EXIST,
                    "Suitable approval with id : " + approvalId + " doesn't exist");
        }
        return retval;
    }

    @Override
    public int isApproved(AuthenticationToken admin, int approvalId, int step)
            throws ApprovalException, ApprovalRequestExpiredException {
        if (log.isTraceEnabled()) {
            log.trace(">isApproved, approvalId: " + approvalId);
        }
        int retval = ApprovalDataVO.STATUS_EXPIREDANDNOTIFIED;
        Collection<ApprovalData> result = ApprovalData.findByApprovalId(entityManager, approvalId);
        if (result.size() == 0) {
            throw new ApprovalException(ErrorCode.APPROVAL_REQUEST_ID_NOT_EXIST,
                    "Approval request with id : " + approvalId + " does not exist");
        }
        Iterator<ApprovalData> iter = result.iterator();
        while (iter.hasNext()) {
            ApprovalData adl = iter.next();
            retval = isApproved(adl, step);
            if (adl.getStatus() == ApprovalDataVO.STATUS_WAITINGFORAPPROVAL
                    || adl.getStatus() == ApprovalDataVO.STATUS_APPROVED
                    || adl.getStatus() == ApprovalDataVO.STATUS_REJECTED) {
                break;
            }
        }
        if (log.isTraceEnabled()) {
            log.trace("<isApproved, result: " + retval);
        }
        return retval;
    }

    @Override
    public int isApproved(AuthenticationToken admin, int approvalId)
            throws ApprovalException, ApprovalRequestExpiredException {
        return isApproved(admin, approvalId, 0);
    }

    @Override
    public void markAsStepDone(AuthenticationToken admin, int approvalId, int step)
            throws ApprovalException, ApprovalRequestExpiredException {
        if (log.isTraceEnabled()) {
            log.trace(">markAsStepDone, approvalId: " + approvalId + ", step " + step);
        }
        Collection<ApprovalData> result = ApprovalData.findByApprovalId(entityManager, approvalId);
        Iterator<ApprovalData> iter = result.iterator();
        if (result.size() == 0) {
            throw new ApprovalException(ErrorCode.APPROVAL_REQUEST_ID_NOT_EXIST,
                    "Approval request with id : " + approvalId + " does not exist");
        }
        while (iter.hasNext()) {
            ApprovalData adl = iter.next();
            markStepAsDone(adl, step);
        }
        log.trace("<markAsStepDone.");
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public ApprovalDataVO findNonExpiredApprovalRequest(AuthenticationToken admin, int approvalId) {
        ApprovalDataVO retval = null;
        ApprovalData data = findNonExpiredApprovalDataLocal(approvalId);
        if (data != null) {
            retval = getApprovalDataVO(data);
        }
        return retval;
    }

    private ApprovalData findNonExpiredApprovalDataLocal(int approvalId) {
        ApprovalData retval = null;
        Collection<ApprovalData> result = ApprovalData.findByApprovalIdNonExpired(entityManager, approvalId);
        if (log.isDebugEnabled()) {
            log.debug("Found number of approvalIdNonExpired: " + result.size());
        }
        Iterator<ApprovalData> iter = result.iterator();
        while (iter.hasNext()) {
            ApprovalData next = iter.next();
            ApprovalDataVO data = getApprovalDataVO(next);
            if (data.getStatus() == ApprovalDataVO.STATUS_WAITINGFORAPPROVAL
                    || data.getStatus() == ApprovalDataVO.STATUS_APPROVED
                    || data.getStatus() == ApprovalDataVO.STATUS_REJECTED) {
                retval = next;
            }
        }
        return retval;
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public Collection<ApprovalDataVO> findApprovalDataVO(AuthenticationToken admin, int approvalId) {
        log.trace(">findApprovalDataVO");
        ArrayList<ApprovalDataVO> retval = new ArrayList<ApprovalDataVO>();
        Collection<ApprovalData> result = ApprovalData.findByApprovalId(entityManager, approvalId);
        Iterator<ApprovalData> iter = result.iterator();
        while (iter.hasNext()) {
            ApprovalData adl = iter.next();
            retval.add(getApprovalDataVO(adl));
        }
        log.trace("<findApprovalDataVO");
        return retval;
    }

    @TransactionAttribute(TransactionAttributeType.SUPPORTS)
    @Override
    public List<ApprovalDataVO> query(AuthenticationToken admin, Query query, int index, int numberofrows,
            String caAuthorizationString, String endEntityProfileAuthorizationString)
            throws AuthorizationDeniedException, IllegalQueryException {
        log.trace(">query()");
        String customQuery = "";
        // Check if query is legal.
        if (query != null && !query.isLegalQuery()) {
            throw new IllegalQueryException();
        }
        if (query != null) {
            customQuery += query.getQueryString();
        }
        if (!caAuthorizationString.equals("") && query != null) {
            customQuery += " AND " + caAuthorizationString;
        } else {
            customQuery += caAuthorizationString;
        }
        if (StringUtils.isNotEmpty(endEntityProfileAuthorizationString)) {
            if (caAuthorizationString.equals("") && query == null) {
                customQuery += endEntityProfileAuthorizationString;
            } else {
                customQuery += " AND " + endEntityProfileAuthorizationString;
            }
        }
        final List<ApprovalData> approvalDataList = ApprovalData.findByCustomQuery(entityManager, index,
                numberofrows, customQuery);
        final List<ApprovalDataVO> returnData = new ArrayList<ApprovalDataVO>(approvalDataList.size());
        for (ApprovalData approvalData : approvalDataList) {
            returnData.add(getApprovalDataVO(approvalData));
        }
        log.trace("<query()");
        return returnData;
    }

    @Override
    public void sendApprovalNotification(AuthenticationToken admin, String approvalAdminsEmail,
            String approvalNotificationFromAddress, String approvalURL, String notificationSubject,
            String notificationMsg, Integer id, int numberOfApprovalsLeft, Date requestDate,
            ApprovalRequest approvalRequest, Approval approval) {
        if (log.isTraceEnabled()) {
            log.trace(">sendNotification approval notification: id=" + id);
        }
        try {
            AuthenticationToken sendAdmin = admin;
            Certificate requestAdminCert = approvalRequest.getRequestAdminCert();
            String requestAdminDN = null;
            String requestAdminUsername = null;
            if (requestAdminCert != null) {
                requestAdminDN = CertTools.getSubjectDN(requestAdminCert);
                // Try to get username from database
                requestAdminUsername = certificateStoreSession.findUsernameByIssuerDnAndSerialNumber(
                        CertTools.getIssuerDN(requestAdminCert), CertTools.getSerialNumber(requestAdminCert));
            } else {
                requestAdminUsername = intres.getLocalizedMessage("CLITOOL");
                requestAdminDN = "CN=" + requestAdminUsername;
            }

            if (approvalAdminsEmail.equals("") || approvalNotificationFromAddress.equals("")) {
                final String msg = intres.getLocalizedMessage("approval.errornotificationemail", id);
                log.info(msg);
            } else {
                String approvalTypeText = intres
                        .getLocalizedMessage(ApprovalDataVO.APPROVALTYPENAMES[approvalRequest.getApprovalType()]);

                String approvalAdminUsername = null;
                String approvalAdminDN = null;
                String approveComment = null;
                if (approval != null) {
                    // Do we have an approval admin certificate?
                    if (approval.getAdmin() instanceof X509CertificateAuthenticationToken) {
                        X509CertificateAuthenticationToken xtoken = (X509CertificateAuthenticationToken) approval
                                .getAdmin();
                        approvalAdminDN = CertTools.getSubjectDN(xtoken.getCertificate());
                        // Try to get username from database
                        approvalAdminUsername = certificateStoreSession.findUsernameByIssuerDnAndSerialNumber(
                                CertTools.getIssuerDN(xtoken.getCertificate()),
                                CertTools.getSerialNumber(xtoken.getCertificate()));
                    } else {
                        approvalAdminUsername = approval.getAdmin().toString();
                    }
                    approveComment = approval.getComment();
                }
                Integer numAppr = Integer.valueOf(numberOfApprovalsLeft);
                ApprovalNotificationParamGen paramGen = new ApprovalNotificationParamGen(requestDate, id,
                        approvalTypeText, numAppr, approvalURL, approveComment, requestAdminUsername,
                        requestAdminDN, approvalAdminUsername, approvalAdminDN);
                String subject = paramGen.interpolate(notificationSubject);
                String message = paramGen.interpolate(notificationMsg);
                List<String> toList = Arrays.asList(approvalAdminsEmail);
                String sendAdminEmail = null;
                if (sendAdmin instanceof X509CertificateAuthenticationToken) {
                    // Firstly, see if it exists in the certificate
                    X509CertificateAuthenticationToken xtoken = (X509CertificateAuthenticationToken) sendAdmin;
                    // Try to get username from database
                    sendAdminEmail = CertTools.getEMailAddress(xtoken.getCertificate());
                    if (sendAdminEmail == null) {
                        // Secondly, see if it exists locally
                        Certificate certificate = xtoken.getCertificate();
                        String username = certificateStoreSession.findUsernameByIssuerDnAndSerialNumber(
                                CertTools.getIssuerDN(certificate), CertTools.getSerialNumber(certificate));
                        EndEntityInformation endEntityInformation = endEntityAccessSession.findUser(admin,
                                username);
                        if (endEntityInformation != null) {
                            sendAdminEmail = endEntityInformation.getEmail();
                        }
                    }
                }
                if (sendAdminEmail == null || sendAdminEmail.length() == 0) {
                    final String msg = intres.getLocalizedMessage("approval.errornotificationemail", id);
                    log.info(msg);
                } else {
                    toList = Arrays.asList(approvalAdminsEmail, sendAdminEmail);
                }
                MailSender.sendMailOrThrow(approvalNotificationFromAddress, toList, MailSender.NO_CC, subject,
                        message, MailSender.NO_ATTACHMENTS);
                final String msg = intres.getLocalizedMessage("approval.sentnotification", id);
                log.info(msg);
            }
        } catch (Exception e) {
            final String msg = intres.getLocalizedMessage("approval.errornotification", id);
            log.info(msg, e);
        }
        if (log.isTraceEnabled()) {
            log.trace("<sendNotification approval notification: id=" + id);
        }
    }

    private Integer findFreeApprovalId() {
        final ProfileID.DB db = new ProfileID.DB() {
            @Override
            public boolean isFree(int i) {
                return ApprovalData.findByApprovalId(ApprovalSessionBean.this.entityManager, i).size() == 0;
            }
        };
        return Integer.valueOf(ProfileID.getNotUsedID(db));
    }

    /**
     * Method that rejects an approval. After someone have rejected the request no-one else can approve it
     */
    private void reject(final ApprovalData approvalData, final Approval approval)
            throws ApprovalRequestExpiredException, ApprovalException {
        if (approvalData.haveRequestOrApprovalExpired()) {
            throw new ApprovalRequestExpiredException();
        }
        if (approvalData.getStatus() != ApprovalDataVO.STATUS_WAITINGFORAPPROVAL) {
            throw new ApprovalException("Wrong status of approval request.");
        }
        final int numberofapprovalsleft = approvalData.getRemainingapprovals() - 1;
        if (numberofapprovalsleft < 0) {
            throw new ApprovalException("Error already enough approvals have been done on this request.");
        }
        approvalData.setRemainingapprovals(0);
        final Collection<Approval> approvals = getApprovals(approvalData);
        approvals.add(approval);
        setApprovals(approvalData, approvals);
        if (getApprovalRequest(approvalData).isExecutable()) {
            approvalData.setStatus(ApprovalDataVO.STATUS_EXECUTIONDENIED);
            approvalData.setExpireDate(new Date());
        } else {
            approvalData.setStatus(ApprovalDataVO.STATUS_REJECTED);
            approvalData
                    .setExpiredate((new Date()).getTime() + getApprovalRequest(approvalData).getApprovalValidity());
        }
    }

    /**
     * Method used to mark an non-executable approval as done if the last step is performed will the status be set as expired.
     * 
     * @throws ApprovalRequestExpiredException if the step have already been executed
     */
    private void markStepAsDone(final ApprovalData approvalData, final int step)
            throws ApprovalRequestExpiredException {
        final ApprovalRequest ar = getApprovalRequest(approvalData);
        if (!ar.isExecutable() && approvalData.getStatus() == ApprovalDataVO.STATUS_APPROVED) {
            if (!ar.isStepDone(step)) {
                ar.markStepAsDone(step);
                setApprovalRequest(approvalData, ar);
                if (step == ar.getNumberOfApprovalSteps() - 1) {
                    approvalData.setStatus(ApprovalDataVO.STATUS_EXPIRED);
                }
            } else {
                throw new ApprovalRequestExpiredException("Error step " + step + " of approval with id "
                        + approvalData.getApprovalid() + " have alread been performed");
            }
        }
    }

    /**
     * Method used by the requestadmin to check if an approval request have been approved
     * 
     * @return the number of approvals left, 0 if approved othervis is the ApprovalDataVO.STATUS constants returned indicating the statys.
     * @throws ApprovalRequestExpiredException if the request or approval have expired, the status will be EXPIREDANDNOTIFIED in this case.
     */
    private int isApproved(final ApprovalData approvalData, final int step) throws ApprovalRequestExpiredException {
        if (getApprovalRequest(approvalData).isStepDone(step)) {
            return ApprovalDataVO.STATUS_EXPIRED;
        }
        if (approvalData.haveRequestOrApprovalExpired()) {
            if (approvalData.getStatus() != ApprovalDataVO.STATUS_EXPIREDANDNOTIFIED
                    && approvalData.getStatus() != ApprovalDataVO.STATUS_EXECUTED
                    && approvalData.getStatus() != ApprovalDataVO.STATUS_EXECUTIONDENIED
                    && approvalData.getStatus() != ApprovalDataVO.STATUS_EXECUTIONFAILED) {
                approvalData.setStatus(ApprovalDataVO.STATUS_EXPIREDANDNOTIFIED);
                throw new ApprovalRequestExpiredException();
            }
            return ApprovalDataVO.STATUS_EXPIREDANDNOTIFIED;
        }
        if (approvalData.getStatus() == ApprovalDataVO.STATUS_WAITINGFORAPPROVAL) {
            return approvalData.getRemainingapprovals();
        }
        return approvalData.getStatus();
    }

    /**
     * Method that returns the approval data. This method currently extracts the ApprovalRequest object.
     */
    private AuthenticationToken getRequestAuthenticationToken(final ApprovalData approvalData) {
        return getApprovalRequest(approvalData).getRequestAdmin();
    }

    @Override
    public ApprovalRequest getApprovalRequest(final ApprovalData approvalData) {
        return ApprovalDataUtil.getApprovalRequest(approvalData.getRequestdata());
    }

    private final void setApprovalRequest(final ApprovalData approvalData, final ApprovalRequest approvalRequest) {
        try {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            final ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(approvalRequest);
            oos.flush();
            approvalData.setRequestdata(new String(Base64.encode(baos.toByteArray(), false)));
        } catch (IOException e) {
            log.error("Error building approval request.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public ApprovalDataVO getApprovalDataVO(ApprovalData approvalData) {
        approvalData.haveRequestOrApprovalExpired();
        return new ApprovalDataVO(approvalData.getId(), approvalData.getApprovalid(),
                approvalData.getApprovaltype(), approvalData.getEndentityprofileid(), approvalData.getCaid(),
                approvalData.getReqadmincertissuerdn(), approvalData.getReqadmincertsn(), approvalData.getStatus(),
                getApprovals(approvalData), getApprovalRequest(approvalData), approvalData.getRequestDate(),
                approvalData.getExpireDate(), approvalData.getRemainingapprovals());
    }

    @Override
    public Collection<Approval> getApprovals(ApprovalData approvalData) {
        return ApprovalDataUtil.getApprovals(approvalData.getApprovaldata());
    }

    @Override
    public void setApprovals(ApprovalData approvalData, final Collection<Approval> approvals) {
        try {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            final ObjectOutputStream oos = new ObjectOutputStream(baos);
            final int size = approvals.size();
            oos.writeInt(size);
            final Iterator<Approval> iter = approvals.iterator();
            while (iter.hasNext()) {
                final Approval next = iter.next();
                oos.writeObject(next);
            }
            oos.flush();
            approvalData.setApprovaldata(new String(Base64.encode(baos.toByteArray(), false)));
        } catch (IOException e) {
            log.error("Error building approvals.", e);
            throw new RuntimeException(e);
        }
    }
}