/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License  as
 * published by the Free Software Foundation, either version 3 of  the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero  General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public  License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * UserAndRoleManagementServiceImpl.java
 *
 * This file was auto-generated from WSDL
 * by the Apache Axis 1.3 Oct 05, 2005 (05:23:37 EDT) WSDL2Java emitter.
 */

package com.jaspersoft.jasperserver.ws.axis2.authority;

import com.jaspersoft.jasperserver.ws.authority.WSUser;
import com.jaspersoft.jasperserver.ws.authority.WSUserSearchCriteria;
import com.jaspersoft.jasperserver.ws.authority.WSRole;
import com.jaspersoft.jasperserver.ws.authority.WSRoleSearchCriteria;
import com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService;
import com.jaspersoft.jasperserver.api.metadata.user.service.TenantService;
import com.jaspersoft.jasperserver.api.metadata.user.domain.User;
import com.jaspersoft.jasperserver.api.metadata.user.domain.Role;
import com.jaspersoft.jasperserver.api.metadata.user.domain.Tenant;
import com.jaspersoft.jasperserver.api.metadata.user.domain.client.RoleImpl;
import com.jaspersoft.jasperserver.api.engine.common.service.SecurityContextProvider;
import com.jaspersoft.jasperserver.api.logging.audit.context.AuditContext;
import com.jaspersoft.jasperserver.api.logging.audit.domain.AuditEvent;
import com.jaspersoft.jasperserver.war.common.ConfigurationBean;
import com.jaspersoft.jasperserver.war.common.JasperServerUtil;
import org.apache.axis.AxisFault;

import java.util.*;

public class UserAndRoleManagementServiceImpl implements UserAndRoleManagementService {

    protected UserAuthorityService userAuthorityService;
    private TenantService tenantService;
    private AuditContext auditContext;

    private SecurityContextProvider securityContextProvider;
    private ConfigurationBean conf;
    private List<WSRole> defaultRoles;

    protected void createAuditEvent(final String auditEventType) {
        auditContext.doInAuditContext(new AuditContext.AuditContextCallback() {
            public void execute() {
                auditContext.createAuditEvent(auditEventType);
            }
        });
    }

    protected void addExceptionToAuditEvent(final String auditEventType, final Exception exception) {
        auditContext.doInAuditContext(auditEventType, new AuditContext.AuditContextCallbackWithEvent() {
            public void execute(AuditEvent auditEvent) {
                auditContext.addPropertyToAuditEvent("exception", exception, auditEvent);
            }
        });
    }

    public WSUser[] findUsers(WSUserSearchCriteria criteria) throws AxisFault {

        if(criteria == null) {
            throw new AxisFault("User search criteria is null.");
        }

        if (!doesContextUserHasAccessToTenant(criteria.getTenantId())) {
            throw new AxisFault("Access is denied.");
        }

        List<User> userList = new ArrayList<User>();

        int maxRecords = criteria.getMaxRecords();
        Boolean includeSubOrgs = criteria.getIncludeSubOrgs();
        Set tenantsCriteriaSet = getTenantsCriteriaSet(criteria.getTenantId(), (includeSubOrgs == null || includeSubOrgs));
        List<Role> requiredRoles = RoleBeanTraslator.toRoleList(criteria.getRequiredRoles());

        List result;
        if (maxRecords > 0) {
            result = userAuthorityService.getTenantUsers(null, tenantsCriteriaSet, criteria.getName(), 0, maxRecords);
        } else {
            result = userAuthorityService.getTenantUsers(null, tenantsCriteriaSet, criteria.getName());
        }

        userList = getUsersWithRoles(result, requiredRoles);

        return UserBeanTraslator.toWSUserArray(userList);
    }

    private List<User> getUsersWithRoles(List users, List<Role> roles) {
        if (roles == null) {
            return new ArrayList<User>(users);
        }

        List<User> userList = new ArrayList<User>();

        for(Object o : users) {
            User u = (User) o;

            if (isUserHasRoles(u, roles)) {
                 userList.add(u);
            }
        }

        return userList;
    }

    private boolean isUserHasRoles(User user, List<Role> roles) {
        boolean isUserHasRoles = true;

        for(Role r : roles) {
            boolean hasRole = false;

            for(Object o : user.getRoles()) {
                Role ur = (Role) o;

                boolean isNameEquals = ur.getRoleName().equals(r.getRoleName());
                boolean isTenantEquals = isTenantEquals(ur.getTenantId(), r.getTenantId());

                if (isNameEquals && isTenantEquals) {
                    hasRole = true;
                    break;
                }
            }

            if (!hasRole) {
                isUserHasRoles = false;
                break;
            }
        }

        return isUserHasRoles;
    }

    private boolean isTenantEquals(String tenantId1, String tenantId2) {
        if (tenantId1 == null) {
            return (tenantId2 == null);
        } else {
            return tenantId1.equals(tenantId2);
        }
    }

    public WSUser putUser(WSUser user) throws AxisFault {

        String auditEventType = "createUser";

        try {
            if(user == null) {
                createAuditEvent(auditEventType);
                throw new AxisFault("User is null.");
            }

            User aUser = UserBeanTraslator.toUser(user);

            String nameWithoutNotSupportedSymbols =
                    aUser.getUsername().replaceAll(conf.getUserNameNotSupportedSymbols(), "");

            if (nameWithoutNotSupportedSymbols.length() != aUser.getUsername().length()) {
                createAuditEvent(auditEventType);
                throw new AxisFault("User name contains not supported symbols");
            }

            User existedUser = getUser(aUser);
            if (existedUser == null) {
                auditEventType = "createUser";
            } else {
                auditEventType = "updateUser";
            }
            createAuditEvent(auditEventType);

            if (!isEmailValid(aUser)) {
                throw new AxisFault("Email address contains not supported symbols");
            }

            if (aUser.getPassword() == null || aUser.getPassword().trim().length() == 0) {
                throw new AxisFault("Password should not be empty");
            }

            if (!doesContextUserHasAccessToTenant(aUser.getTenantId())) {
                throw new AxisFault("Access is denied.");
            }

            if (existedUser == null) {
                if (defaultRoles != null) {
                    for (WSRole wsRole : defaultRoles) {
                        Role role = getRole(RoleBeanTraslator.toRole(wsRole));

                        addDefaultRoleToUser(role, aUser);
                    }
                }
            }

            try {
                userAuthorityService.putUser(null, aUser);
            } catch (Exception e) {
                handleUnexpectedException(e, aUser.getUsername());
            }

            return UserBeanTraslator.toWSUser(aUser);
        }
        catch (AxisFault axisFault) {
            addExceptionToAuditEvent(auditEventType, axisFault);
            throw axisFault;
        }
    }

    private boolean isEmailValid(User user) throws AxisFault {
        String email = user.getEmailAddress();
        if(email == null) {
            return true;
        }

        if(email.trim().length() > 0) {
            if(!JasperServerUtil.regExValidateEmail(user.getEmailAddress())) {
                return false;
            }
        } else  {
            if(email.trim().length() != email.length()) {
                return false;
            }
        }

        return true;
    }

    private void addDefaultRoleToUser(Role role, User user) {
        if (role == null) {
            return;
        }

        boolean isUserHasRole = false;

        for (Object o : user.getRoles()) {
            Role r = (Role) o;

            boolean isNameEquals = role.getRoleName().equals(r.getRoleName());
            boolean isTenantEquals = isTenantEquals(role.getTenantId(), r.getTenantId());

            isUserHasRole = (isNameEquals && isTenantEquals);

            if (isUserHasRole) {
                return;
            }
        }

        user.addRole(role);
    }

    protected User getUser(User user) {
        return userAuthorityService.getUser(null, user.getUsername());
    }
    
    public void deleteUser(WSUser user) throws AxisFault {

        createAuditEvent("deleteUser");

        try {
            if(user == null) {
                throw new AxisFault("User is null.");
            }

            if (!doesContextUserHasAccessToTenant(user.getTenantId())) {
                throw new AxisFault("Access is denied.");
            }

            deleteUser(user.getUsername(), user.getTenantId());
        } catch (AxisFault axisFault) {
            addExceptionToAuditEvent("deleteUser", axisFault);
            throw axisFault;
        }
    }

    protected void deleteUser(String username, String tenantId) {
        userAuthorityService.deleteUser(null, username);
    }

    public WSRole[] findRoles(WSRoleSearchCriteria criteria) throws AxisFault {

        if(criteria == null) {
            throw new AxisFault("Role search criteria is null.");
        }

        if (!doesContextUserHasAccessToTenant(criteria.getTenantId()) && !isRootTenant(criteria.getTenantId())) {
            throw new AxisFault("Access is denied.");
        }
        
        int maxRecords = criteria.getMaxRecords();
        Boolean includeSubOrgs = criteria.getIncludeSubOrgs();
        Set tenantsCriteriaSet = getTenantsCriteriaSet(criteria.getTenantId(), (includeSubOrgs == null || includeSubOrgs));

        List result;
        if (maxRecords > 0) {
            result = userAuthorityService.getTenantRoles(null, tenantsCriteriaSet, criteria.getRoleName(), 0, maxRecords);
        } else {
            result = userAuthorityService.getTenantRoles(null, tenantsCriteriaSet, criteria.getRoleName());
        }

        return RoleBeanTraslator.toWSRoleArray(result);
    }

    public WSRole putRole(WSRole role) throws AxisFault {

        String auditEventType = "createRole";
        
        try {
            if(role == null) {
                createAuditEvent(auditEventType);
                throw new AxisFault("Role is null.");
            }

            Role aRole = RoleBeanTraslator.toRole(role);

            String nameWithoutNotSupportedSymbols =
                    aRole.getRoleName().replaceAll(conf.getRoleNameNotSupportedSymbols(), "");

            if (nameWithoutNotSupportedSymbols.length() != aRole.getRoleName().length()) {
                createAuditEvent(auditEventType);
                throw new AxisFault("Role name contains not supported symbols");
            }

            Role existedRole = getRole(aRole);
            if (existedRole == null) {
                auditEventType = "createRole";
            } else {
                auditEventType = "updateRole";
            }
            createAuditEvent(auditEventType);

            if (!doesContextUserHasAccessToTenant(aRole.getTenantId())) {
                throw new AxisFault("Access is denied.");
            }

            userAuthorityService.putRole(null, aRole);
            Role r = getRole(aRole);

            if (r != null) {
                return RoleBeanTraslator.toWSRole(r);
            } else {
                throw new AxisFault("Error while putting role : " + aRole.getRoleName());
            }
        } catch (AxisFault axisFault) {
            addExceptionToAuditEvent(auditEventType, axisFault);
            throw axisFault;
        }
    }

    public WSRole updateRoleName(WSRole oldRole, String newName) throws AxisFault {

        createAuditEvent("updateRole");

        try {
            if(oldRole == null) {
                throw new AxisFault("Role is null.");
            }

            if(newName == null) {
                throw new AxisFault("New name is null.");
            }

            Role aRole = RoleBeanTraslator.toRole(oldRole);

            String nameWithoutNotSupportedSymbols = newName.replaceAll(conf.getRoleNameNotSupportedSymbols(), "");

            if (nameWithoutNotSupportedSymbols.length() != newName.length()) {
                throw new AxisFault("Role name contains not supported symbols");
            }

            if (!doesContextUserHasAccessToTenant(aRole.getTenantId())) {
                throw new AxisFault("Access is denied.");
            }

            Role existedRole = getRole(aRole);
            if (existedRole == null) {
                throw new AxisFault("Can't find role " + aRole.getRoleName());
            }

            String auditEventType = "updateRole";
            createAuditEvent(auditEventType);

            Role newRole = new RoleImpl();
            newRole.setRoleName(newName);
            newRole.setTenantId(existedRole.getTenantId());
            newRole.setExternallyDefined(existedRole.isExternallyDefined());

            Role r = updateRole(existedRole, newRole);

            if (r != null) {
                return RoleBeanTraslator.toWSRole(r);
            } else {
                throw new AxisFault("Error while putting role : " + aRole.getRoleName());
            }
        } catch (AxisFault axisFault) {
            addExceptionToAuditEvent("updateRole", axisFault);
            throw axisFault;
        }
    }

    protected Role updateRole(Role oldRole, Role newRole) {
        userAuthorityService.updateRole(null, oldRole.getRoleName(), newRole);

        return getRole(newRole);
    }

    protected Role getRole(Role role) {
        return userAuthorityService.getRole(null, role.getRoleName());
    }

    protected Set getTenantsCriteriaSet(String tenantId, boolean includeSubOrgs) throws AxisFault {

        Set tenantIdSet = new HashSet();
        tenantIdSet.add(tenantId);

        if (includeSubOrgs) {
            String id = (tenantId == null) ? TenantService.ORGANIZATIONS : tenantId;
            List allTenants = null;
            try {
                allTenants = tenantService.getAllSubTenantList(null, id);
            } catch (Exception e) {
                throw new AxisFault("Organization '" + tenantId + "' not found.");
            }

            if (allTenants != null) {
                for (Iterator it = allTenants.iterator(); it.hasNext(); ) {
                    Tenant tenant = (Tenant) it.next();

                    tenantIdSet.add(tenant.getId());
                }
            }
        }

        return tenantIdSet;
    }

    public void deleteRole(WSRole role) throws AxisFault {

        createAuditEvent("deleteRole");

        try {
            if(role == null) {
                throw new AxisFault("Role is null.");
            }

            if (!doesContextUserHasAccessToTenant(role.getTenantId())) {
                throw new AxisFault("Access is denied.");
            }

            deleteRole(role.getRoleName(), role.getTenantId());
        } catch (AxisFault axisFault) {
            addExceptionToAuditEvent("deleteRole", axisFault);
            throw axisFault;
        }
    }

    protected void deleteRole(String roleName, String tenantId) {
        userAuthorityService.deleteRole(null, roleName);
    }

    private boolean doesContextUserHasAccessToTenant(String tenantId) throws AxisFault {
        String currentTenantId = securityContextProvider.getContextUser().getTenantId();

        Set tenants = getTenantsCriteriaSet(currentTenantId, true);

        return tenants.contains(tenantId);
    }

    private boolean isRootTenant(String tenantId) throws AxisFault {
        return tenantId == null || TenantService.ORGANIZATIONS.equals(tenantId);
    }

    public void setUserAuthorityService(UserAuthorityService userAuthorityService) {
        this.userAuthorityService = userAuthorityService;
    }

    public void setTenantService(TenantService tenantService) {
        this.tenantService = tenantService;
    }

    public void setAuditContext(AuditContext auditContext) {
        this.auditContext = auditContext;
    }

    public void setDefaultRoles(List<WSRole> defaultRoles) {
        this.defaultRoles = defaultRoles;
    }

    public void setSecurityContextProvider(SecurityContextProvider securityContextProvider) {
        this.securityContextProvider = securityContextProvider;
    }

    public void setConfigurationBean(ConfigurationBean configurationBean) {
        this.conf = configurationBean;
    }

    protected void handleUnexpectedException(Exception unexpectedException, String username) throws AxisFault {
        throw new AxisFault("An unexpected exception has occurred while putting user:" + username);
    }

}
