package alien4cloud.application; import static alien4cloud.dao.FilterUtil.fromKeyValueCouples; import static alien4cloud.dao.FilterUtil.singleKeyFilter; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import javax.annotation.Resource; import org.elasticsearch.common.lang3.ArrayUtils; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import alien4cloud.dao.IGenericSearchDAO; import alien4cloud.events.BeforeApplicationDeletedEvent; import alien4cloud.exception.AlreadyExistException; import alien4cloud.exception.InvalidApplicationNameException; import alien4cloud.exception.NotFoundException; import alien4cloud.model.application.Application; import alien4cloud.model.common.Tag; import alien4cloud.model.deployment.Deployment; import alien4cloud.paas.exception.OrchestratorDisabledException; import alien4cloud.security.AuthorizationUtil; import alien4cloud.security.model.ApplicationRole; import alien4cloud.topology.TopologyUtils; import lombok.extern.slf4j.Slf4j; /** * Service to manage applications. */ @Slf4j @Service public class ApplicationService { @Resource(name = "alien-es-dao") private IGenericSearchDAO alienDAO; @Resource private ApplicationEnvironmentService applicationEnvironmentService; @Resource private ApplicationVersionService applicationVersionService; @Resource private ApplicationEventPublisher publisher; private static final String APPLICATION_NAME_REGEX = "[^/\\\\\\\\]+"; /** * Create a new application and return it's id * * @param user The user that is creating the application (will be APPLICATION_MANAGER) * @param archiveName The unique archive name (and if for the application). * @param name The name of the new application. * @param description The description of the new application. * @return The id of the newly created application. */ public String create(String user, String archiveName, String name, String description) { checkApplicationId(archiveName); checkApplicationName(name); Application application = new Application(); application.setId(archiveName); Map<String, Set<String>> userRoles = Maps.newHashMap(); userRoles.put(user, Sets.newHashSet(ApplicationRole.APPLICATION_MANAGER.toString())); application.setUserRoles(userRoles); application.setName(name); application.setDescription(description); application.setTags(Lists.<Tag> newArrayList()); application.setMetaProperties(Maps.<String, String> newHashMap()); alienDAO.save(application); return archiveName; } private void checkApplicationId(String applicationId) { // Check that it matches the required pattern if (!TopologyUtils.isValidNodeName(applicationId)) { // FIXME throw another exception ? throw new InvalidApplicationNameException("Application id <" + applicationId + "> is not valid. It must not contains any special characters."); } // Check that it doesn't already exists if (alienDAO.findById(Application.class, applicationId) != null) { throw new AlreadyExistException("An application with the given id already exists."); } } private void checkApplicationName(String name) { if (alienDAO.buildQuery(Application.class).setFilters(singleKeyFilter("name", name)).count() > 0) { log.debug("Application name <{}> already exists.", name); throw new AlreadyExistException("An application with the given name already exists."); } if (!Pattern.matches(APPLICATION_NAME_REGEX, name)) { log.debug("Application name <{}> contains forbidden character.", name); throw new InvalidApplicationNameException("Application name <" + name + "> contains forbidden character."); } } /** * Update the name and description of an application. * * @param applicationId The application id. * @param newName The new name for the application. * @param newDescription The new description for the application. */ public void update(String applicationId, String newName, String newDescription) { Application application = checkAndGetApplication(applicationId, ApplicationRole.APPLICATION_MANAGER); if (newName != null && !newName.isEmpty() && !application.getName().equals(newName)) { checkApplicationName(newName); application.setName(newName); } if (newDescription != null) { application.setDescription(newDescription); } alienDAO.save(application); } /** * Get an application from it's id and throw a {@link NotFoundException} in case no application matches the requested id. * * @param applicationId The id of the application to retrieve. * @return The requested application. */ public Application getOrFail(String applicationId) { Application application = alienDAO.findById(Application.class, applicationId); if (application == null) { throw new NotFoundException("Application [" + applicationId + "] cannot be found"); } return application; } /** * Retrieve applications given a list of ids, and a specific context * Only retrieves the authorized ones. * * @param fetchContext The fetch context to recover only the required field (Note that this should be simplified to directly use the given field...). * @param ids array of id of the applications to find * @return Map of Applications that has the given ids and for which the user is authorized (key is application Id), or null if no application matching the * request is found. */ public Map<String, Application> findByIdsIfAuthorized(String fetchContext, String... ids) { List<Application> apps = alienDAO.findByIdsWithContext(Application.class, fetchContext, ids); if (apps == null) { return null; } Map<String, Application> applications = Maps.newHashMap(); Iterator<Application> iterator = apps.iterator(); while (iterator.hasNext()) { Application app = iterator.next(); if (!AuthorizationUtil.hasAuthorizationForApplication(app, ApplicationRole.values())) { iterator.remove(); continue; } applications.put(app.getId(), app); } return applications.isEmpty() ? null : applications; } /** * Delete an existing application from it's id. This method ensures first that there is no running deployment of the application. * * @param applicationId The id of the application to remove. * @return True if the application has been removed, false if not. * @throws alien4cloud.paas.exception.OrchestratorDisabledException */ public boolean delete(String applicationId) throws OrchestratorDisabledException { // ensure that there is no active deployment(s). if (alienDAO.count(Deployment.class, null, fromKeyValueCouples("sourceId", applicationId, "endDate", null)) > 0) { return false; } // delete the application applicationVersionService.deleteByApplication(applicationId); applicationEnvironmentService.deleteByApplication(applicationId); // Clean up other resources publisher.publishEvent(new BeforeApplicationDeletedEvent(this, applicationId)); alienDAO.delete(Application.class, applicationId); return true; } /** * Check if the connected user has at least one application role on the related application with a fail when applicationId is not valid * If no roles mentioned, all {@link ApplicationRole} values will be used (one at least required) * * @param applicationId * @return the related application */ public Application checkAndGetApplication(String applicationId, ApplicationRole... roles) { Application application = getOrFail(applicationId); roles = ArrayUtils.isEmpty(roles) ? ApplicationRole.values() : roles; AuthorizationUtil.checkAuthorizationForApplication(application, roles); return application; } }