/*
* Copyright (C) 2007-2014 Crafter Software Corporation.
*
* This program 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
* (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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.craftercms.profile.services.impl;
import java.util.Collection;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.craftercms.commons.collections.IterableUtils;
import org.craftercms.commons.i10n.I10nLogger;
import org.craftercms.commons.logging.Logged;
import org.craftercms.commons.mongo.DuplicateKeyException;
import org.craftercms.commons.mongo.MongoDataException;
import org.craftercms.commons.mongo.UpdateHelper;
import org.craftercms.commons.security.exception.ActionDeniedException;
import org.craftercms.commons.security.permissions.PermissionEvaluator;
import org.craftercms.profile.api.AccessToken;
import org.craftercms.profile.api.AttributeDefinition;
import org.craftercms.profile.api.Tenant;
import org.craftercms.profile.api.TenantAction;
import org.craftercms.profile.api.exceptions.I10nProfileException;
import org.craftercms.profile.api.exceptions.ProfileException;
import org.craftercms.profile.api.services.ProfileService;
import org.craftercms.profile.api.services.TenantService;
import org.craftercms.profile.exceptions.NoSuchTenantException;
import org.craftercms.profile.exceptions.TenantExistsException;
import org.craftercms.profile.repositories.ProfileRepository;
import org.craftercms.profile.repositories.TenantRepository;
import org.craftercms.profile.utils.db.TenantUpdater;
import org.springframework.beans.factory.annotation.Required;
/**
* Default implementation of {@link org.craftercms.profile.api.services.TenantService}.
*
* @author avasquez
*/
@Logged
public class TenantServiceImpl implements TenantService {
private static final I10nLogger logger = new I10nLogger(TenantServiceImpl.class,
"crafter.profile.messages.logging");
public static final String LOG_KEY_TENANT_CREATED = "profile.tenant.tenantCreated";
public static final String LOG_KEY_TENANT_DELETED = "profile.tenant.tenantDeleted";
public static final String LOG_KEY_VERIFY_NEW_PROFILES_FLAG_SET = "profile.tenant.verifyNewProfilesFlagSet";
public static final String LOG_KEY_ROLES_ADDED = "profile.tenant.rolesAdded";
public static final String LOG_KEY_ROLES_REMOVED = "profile.tenant.rolesRemoved";
public static final String LOG_KEY_ATTRIBUTE_DEFINITIONS_ADDED = "profile.tenant.attributeDefinitionsAdded";
public static final String LOG_KEY_ATTRIBUTE_DEFINITIONS_UPDATED = "profile.tenant.attributeDefinitionsUpdated";
public static final String LOG_KEY_ATTRIBUTE_DEFINITIONS_REMOVED = "profile.tenant.attributeDefinitionsRemoved";
public static final String ERROR_KEY_CREATE_TENANT_ERROR = "profile.tenant.createTenantError";
public static final String ERROR_KEY_GET_TENANT_ERROR = "profile.tenant.getTenantError";
public static final String ERROR_KEY_UPDATE_TENANT_ERROR = "profile.tenant.updateTenantError";
public static final String ERROR_KEY_DELETE_TENANT_ERROR = "profile.tenant.deleteTenantError";
public static final String ERROR_KEY_GET_TENANT_COUNT_ERROR = "profile.tenant.getTenantCountError";
public static final String ERROR_KEY_GET_ALL_TENANTS_ERROR = "profile.tenant.getAllTenantsError";
public static final String ERROR_KEY_DELETE_ALL_PROFILES_ERROR = "profile.profile.deleteAll";
public static final String ERROR_KEY_REMOVE_ROLE_FROM_ALL_PROFILES_ERROR = "profile.role.removeRoleFromAll";
public static final String ERROR_KEY_REMOVE_ATTRIBUTE_FROM_ALL_PROFILES_ERROR = "profile.attribute" +
".removeAttributeFromAllError";
public static final String ERROR_KEY_ADD_DEFAULT_VALUE_ERROR = "profile.attribute.addDefaultValueError";
protected PermissionEvaluator<AccessToken, String> tenantPermissionEvaluator;
protected PermissionEvaluator<AccessToken, AttributeDefinition> attributePermissionEvaluator;
protected TenantRepository tenantRepository;
protected ProfileRepository profileRepository;
protected ProfileService profileService;
@Required
public void setTenantPermissionEvaluator(PermissionEvaluator<AccessToken, String> tenantPermissionEvaluator) {
this.tenantPermissionEvaluator = tenantPermissionEvaluator;
}
@Required
public void setAttributePermissionEvaluator(
PermissionEvaluator<AccessToken, AttributeDefinition> attributePermissionEvaluator) {
this.attributePermissionEvaluator = attributePermissionEvaluator;
}
@Required
public void setTenantRepository(TenantRepository tenantRepository) {
this.tenantRepository = tenantRepository;
}
@Required
public void setProfileRepository(ProfileRepository profileRepository) {
this.profileRepository = profileRepository;
}
@Required
public void setProfileService(ProfileService profileService) {
this.profileService = profileService;
}
@Override
public Tenant createTenant(Tenant tenant) throws ProfileException {
checkIfTenantActionIsAllowed(null, TenantAction.CREATE_TENANT);
// Make sure ID is null, we want it auto-generated
tenant.setId(null);
try {
tenantRepository.insert(tenant);
} catch (DuplicateKeyException e) {
throw new TenantExistsException(tenant.getName());
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_CREATE_TENANT_ERROR, e, tenant.getName());
}
logger.debug(LOG_KEY_TENANT_CREATED, tenant);
return tenant;
}
@Override
public Tenant getTenant(String name) throws ProfileException {
checkIfTenantActionIsAllowed(name, TenantAction.READ_TENANT);
try {
return tenantRepository.findByName(name);
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_GET_TENANT_ERROR, e, name);
}
}
@Override
public Tenant updateTenant(final Tenant tenant) throws ProfileException {
final String tenantName = tenant.getName();
Tenant updatedTenant = updateTenant(tenant.getName(), new UpdateCallback() {
@Override
public void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException {
Tenant originalTenant = tenantUpdater.getTenant();
Collection<String> originalRoles = originalTenant.getAvailableRoles();
Collection<String> newRoles = tenant.getAvailableRoles();
Collection<AttributeDefinition> originalDefinitions = originalTenant.getAttributeDefinitions();
Collection<AttributeDefinition> newDefinitions = tenant.getAttributeDefinitions();
Collection<String> removedRoles = CollectionUtils.subtract(originalRoles, newRoles);
Collection<AttributeDefinition> removedDefinitions = CollectionUtils.subtract(originalDefinitions,
newDefinitions);
for (String removedRole : removedRoles) {
removeRoleFromProfiles(tenantName, removedRole);
}
for (AttributeDefinition removedDefinition : removedDefinitions) {
removeAttributeFromProfiles(tenantName, removedDefinition.getName());
}
tenantUpdater.setVerifyNewProfiles(tenant.isVerifyNewProfiles());
tenantUpdater.setSsoEnabled(tenant.isSsoEnabled());
tenantUpdater.setAvailableRoles(tenant.getAvailableRoles());
tenantUpdater.setAttributeDefinitions(tenant.getAttributeDefinitions());
}
});
for (AttributeDefinition definition : updatedTenant.getAttributeDefinitions()) {
addDefaultValue(tenantName, definition.getName(), definition.getDefaultValue());
}
return updatedTenant;
}
@Override
public void deleteTenant(String name) throws ProfileException {
checkIfTenantActionIsAllowed(name, TenantAction.DELETE_TENANT);
try {
profileRepository.removeAll(name);
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_DELETE_ALL_PROFILES_ERROR, e, name);
}
try {
tenantRepository.removeByName(name);
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_DELETE_TENANT_ERROR, e, name);
}
logger.debug(LOG_KEY_TENANT_DELETED, name);
}
@Override
public long getTenantCount() throws ProfileException {
checkIfTenantActionIsAllowed(null, TenantAction.READ_TENANT);
try {
return tenantRepository.count();
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_GET_TENANT_COUNT_ERROR, e);
}
}
@Override
public List<Tenant> getAllTenants() throws ProfileException {
checkIfTenantActionIsAllowed(null, TenantAction.READ_TENANT);
try {
return IterableUtils.toList(tenantRepository.findAll());
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_GET_ALL_TENANTS_ERROR, e);
}
}
@Override
public Tenant verifyNewProfiles(String tenantName, final boolean verify) throws ProfileException {
Tenant tenant = updateTenant(tenantName, new UpdateCallback() {
@Override
public void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException {
tenantUpdater.setVerifyNewProfiles(verify);
}
});
logger.debug(LOG_KEY_VERIFY_NEW_PROFILES_FLAG_SET, tenantName, verify);
return tenant;
}
@Override
public Tenant addRoles(String tenantName, final Collection<String> roles) throws ProfileException {
Tenant tenant = updateTenant(tenantName, new UpdateCallback() {
@Override
public void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException {
tenantUpdater.addAvailableRoles(roles);
}
});
logger.debug(LOG_KEY_ROLES_ADDED, roles, tenantName);
return tenant;
}
@Override
public Tenant removeRoles(final String tenantName, final Collection<String> roles) throws ProfileException {
Tenant tenant = updateTenant(tenantName, new UpdateCallback() {
@Override
public void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException {
for (String role : roles) {
removeRoleFromProfiles(tenantName, role);
}
tenantUpdater.removeAvailableRoles(roles);
}
});
logger.debug(LOG_KEY_ROLES_REMOVED, roles, tenantName);
return tenant;
}
@Override
public Tenant addAttributeDefinitions(final String tenantName,
final Collection<AttributeDefinition> attributeDefinitions) throws
ProfileException {
Tenant tenant = updateTenant(tenantName, new UpdateCallback() {
@Override
public void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException {
tenantUpdater.addAttributeDefinitions(attributeDefinitions);
}
});
for (AttributeDefinition definition : tenant.getAttributeDefinitions()) {
addDefaultValue(tenantName, definition.getName(), definition.getDefaultValue());
}
logger.debug(LOG_KEY_ATTRIBUTE_DEFINITIONS_ADDED, attributeDefinitions, tenantName);
return tenant;
}
@Override
public Tenant updateAttributeDefinitions(final String tenantName,
final Collection<AttributeDefinition> attributeDefinitions) throws
ProfileException {
Tenant tenant = updateTenant(tenantName, new UpdateCallback() {
@Override
public void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException {
tenantUpdater.updateAttributeDefinitions(attributeDefinitions);
}
});
logger.debug(LOG_KEY_ATTRIBUTE_DEFINITIONS_UPDATED, attributeDefinitions, tenantName);
return tenant;
}
@Override
public Tenant removeAttributeDefinitions(final String tenantName,
final Collection<String> attributeNames) throws ProfileException {
for (String attributeName : attributeNames) {
removeAttributeFromProfiles(tenantName, attributeName);
}
Tenant tenant = updateTenant(tenantName, new UpdateCallback() {
@Override
public void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException {
tenantUpdater.removeAttributeDefinitions(attributeNames);
}
});
logger.debug(LOG_KEY_ATTRIBUTE_DEFINITIONS_REMOVED, attributeNames, tenantName);
return tenant;
}
protected void checkIfTenantActionIsAllowed(String tenantName, TenantAction action) {
if (!tenantPermissionEvaluator.isAllowed(tenantName, action.toString())) {
if (tenantName != null) {
throw new ActionDeniedException(action.toString(), "tenant \"" + tenantName + "\"");
} else {
throw new ActionDeniedException(action.toString());
}
}
}
protected Tenant updateTenant(String tenantName, UpdateCallback callback) throws ProfileException {
Tenant tenant = getTenant(tenantName);
if (tenant != null) {
checkIfTenantActionIsAllowed(tenantName, TenantAction.UPDATE_TENANT);
UpdateHelper updateHelper = new UpdateHelper();
TenantUpdater tenantUpdater = new TenantUpdater(tenant, updateHelper, tenantRepository);
callback.doWithTenant(tenantUpdater);
try {
tenantUpdater.update();
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_UPDATE_TENANT_ERROR, e, tenant.getName());
}
} else {
throw new NoSuchTenantException(tenantName);
}
return tenant;
}
protected interface UpdateCallback {
void doWithTenant(TenantUpdater tenantUpdater) throws ProfileException;
}
protected void removeRoleFromProfiles(String tenantName, String role) throws ProfileException {
try {
profileRepository.removeRoleFromAll(tenantName, role);
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_REMOVE_ROLE_FROM_ALL_PROFILES_ERROR, e, role, tenantName);
}
}
protected void removeAttributeFromProfiles(String tenantName, String attributeName) throws ProfileException {
try {
profileRepository.removeAttributeFromAll(tenantName, attributeName);
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_REMOVE_ATTRIBUTE_FROM_ALL_PROFILES_ERROR, e, attributeName,
tenantName);
}
}
protected void addDefaultValue(String tenantName, String attributeName,
Object defaultValue) throws ProfileException {
if (defaultValue != null) {
try {
profileRepository.updateAllWithDefaultValue(tenantName, attributeName, defaultValue);
} catch (MongoDataException e) {
throw new I10nProfileException(ERROR_KEY_ADD_DEFAULT_VALUE_ERROR, e, attributeName, tenantName);
}
}
}
}