package alien4cloud.rest.tags;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Resource;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import io.swagger.annotations.Api;
import org.apache.commons.collections.MapUtils;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import alien4cloud.audit.annotation.Audit;
import alien4cloud.dao.IGenericSearchDAO;
import alien4cloud.dao.model.FacetedSearchResult;
import alien4cloud.dao.model.GetMultipleDataResult;
import alien4cloud.exception.AlreadyExistException;
import alien4cloud.exception.NotFoundException;
import alien4cloud.model.application.Application;
import alien4cloud.model.common.IMetaProperties;
import alien4cloud.model.common.MetaPropConfiguration;
import alien4cloud.model.orchestrators.locations.Location;
import alien4cloud.rest.component.SearchRequest;
import alien4cloud.rest.model.RestErrorBuilder;
import alien4cloud.rest.model.RestErrorCode;
import alien4cloud.rest.model.RestResponse;
import alien4cloud.rest.model.RestResponseBuilder;
import alien4cloud.tosca.container.validation.ToscaSequence;
import alien4cloud.utils.MapUtil;
import alien4cloud.utils.PropertyUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping({ "/rest/metaproperties", "/rest/v1/metaproperties", "/rest/latest/metaproperties" })
@Api
public class TagConfigurationController {
@Resource
private Validator validator;
@Resource(name = "alien-es-dao")
private IGenericSearchDAO dao;
/**
* Throw a not found exception if we found another tag with the same name, the same target
* but a different id
*
* @param name
* @param target
* @param id
*/
private void ensureNameAndTypeUnicity(final String name, final String target, final String id) {
@SuppressWarnings("unchecked")
GetMultipleDataResult<MetaPropConfiguration> result = dao.facetedSearch(MetaPropConfiguration.class, null,
MapUtil.newHashMap(new String[] { "name", "target" }, new String[][] { new String[] { name }, new String[] { target } }), null, 0, 5);
if (result.getData().length > 0 && !result.getData()[0].getId().equals(id)) {
log.debug("An other tag with name <{}> and target <{}> already exists", target, name);
throw new AlreadyExistException("An other tag with name " + name + " and target " + target + " already exists");
}
}
@ApiOperation(value = "Save tag configuration.")
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
@Audit
public RestResponse<TagConfigurationSaveResponse> saveConfiguration(@RequestBody MetaPropConfiguration configuration) {
if (configuration.getName() != null && configuration.getTarget() != null) {
ensureNameAndTypeUnicity(configuration.getName(), configuration.getTarget(), configuration.getId());
}
if (configuration.getId() == null) {
configuration.setId(UUID.randomUUID().toString());
}
Set<ConstraintViolation<MetaPropConfiguration>> violations = validator.validate(configuration, ToscaSequence.class);
if (violations != null && !violations.isEmpty()) {
Set<TagConfigurationValidationError> violationDTOs = Sets.newHashSet();
for (ConstraintViolation<MetaPropConfiguration> violation : violations) {
TagConfigurationValidationError violationDTO = new TagConfigurationValidationError();
violationDTO.setError(violation.getMessage());
violationDTO.setPath(violation.getPropertyPath().toString());
violationDTOs.add(violationDTO);
}
return RestResponseBuilder.<TagConfigurationSaveResponse> builder().data(new TagConfigurationSaveResponse(null, violationDTOs))
.error(RestErrorBuilder.builder(RestErrorCode.PROPERTY_CONSTRAINT_VIOLATION_ERROR).message("Invalid tag configuration").build()).build();
} else {
dao.save(configuration);
// for each resource in the targeted class, add the new meta property with default value
switch (configuration.getTarget().toString()) {
case "application":
addMetaPropertyToResources(Application.class, dao, configuration);
break;
case "location":
addMetaPropertyToResources(Location.class, dao, configuration);
break;
// TODO : case environment
default:
break;
}
return RestResponseBuilder.<TagConfigurationSaveResponse> builder().data(new TagConfigurationSaveResponse(configuration.getId(), null)).build();
}
}
/**
* save the meta properties of the element
*
* @param mpClass class of resources to update with new meta property
* @param dao IGenericSearchDAO object to access ES
* @param configuration configuration of new meta property
*/
private <T extends IMetaProperties> void addMetaPropertyToResources(Class<T> mpClass, IGenericSearchDAO dao, MetaPropConfiguration configuration) {
GetMultipleDataResult<T> result = dao.find(mpClass, null, Integer.MAX_VALUE);
for (T element : result.getData()) {
if (element.getMetaProperties() == null) {
element.setMetaProperties(Maps.<String, String> newHashMap());
}
PropertyUtil.setScalarDefaultValueOrNull(element.getMetaProperties(), configuration.getId(), configuration.getDefault());
dao.save(element);
log.debug("Adding meta property <{}> to a resource of type <{}> ", configuration.getName(), element.getClass());
}
}
private <T extends IMetaProperties> void removeMetaPropertyFromResources(Class<T> mpClass, IGenericSearchDAO dao, MetaPropConfiguration configuration) {
GetMultipleDataResult<T> result = dao.find(mpClass, null, Integer.MAX_VALUE);
for (T element : result.getData()) {
if (MapUtils.isNotEmpty(element.getMetaProperties())) {
element.getMetaProperties().remove(configuration.getId());
}
dao.save(element);
log.debug("Adding meta property <{}> to a resource of type <{}> ", configuration.getName(), element.getClass());
}
}
@ApiOperation(value = "Search for tag configurations registered in ALIEN.")
@RequestMapping(value = "/search", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()")
public RestResponse<FacetedSearchResult> search(@RequestBody SearchRequest request) {
FacetedSearchResult result = dao.facetedSearch(MetaPropConfiguration.class, request.getQuery(), request.getFilters(), null, request.getFrom(),
request.getSize());
return RestResponseBuilder.<FacetedSearchResult> builder().data(result).build();
}
@ApiOperation(value = "Remove tag configuration.")
@RequestMapping(value = "/{tagConfigurationId}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
@Audit
public RestResponse<Void> removeConfiguration(@PathVariable String tagConfigurationId) {
MetaPropConfiguration configuration = dao.findById(MetaPropConfiguration.class, tagConfigurationId);
if (configuration == null) {
throw new NotFoundException("Configuration is not found");
}
switch (configuration.getTarget().toString()) {
case "application":
removeMetaPropertyFromResources(Application.class, dao, configuration);
break;
case "location":
removeMetaPropertyFromResources(Location.class, dao, configuration);
break;
// TODO : case environment
default:
break;
}
dao.delete(MetaPropConfiguration.class, tagConfigurationId);
return RestResponseBuilder.<Void> builder().build();
}
@ApiOperation(value = "Get tag configuration.")
@RequestMapping(value = "/{tagConfigurationId}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('ADMIN')")
public RestResponse<MetaPropConfiguration> getConfiguration(@PathVariable String tagConfigurationId) {
MetaPropConfiguration configuration = dao.findById(MetaPropConfiguration.class, tagConfigurationId);
if (configuration == null) {
throw new NotFoundException("Configuration is not found");
}
return RestResponseBuilder.<MetaPropConfiguration> builder().data(configuration).build();
}
}