package alien4cloud.rest.component; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.annotation.Resource; import alien4cloud.common.TagService; import io.swagger.annotations.Api; import org.alien4cloud.tosca.catalog.CatalogVersionResult; import org.alien4cloud.tosca.catalog.index.IToscaTypeSearchService; import org.alien4cloud.tosca.model.types.AbstractToscaType; import org.alien4cloud.tosca.model.types.NodeType; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.google.common.collect.Lists; import alien4cloud.Constants; import alien4cloud.audit.annotation.Audit; import alien4cloud.dao.IGenericSearchDAO; import alien4cloud.dao.model.FacetedSearchResult; import alien4cloud.dao.model.GetMultipleDataResult; import alien4cloud.model.common.Tag; import alien4cloud.rest.model.RestError; import alien4cloud.rest.model.RestErrorBuilder; import alien4cloud.rest.model.RestErrorCode; import alien4cloud.rest.model.RestResponse; import alien4cloud.rest.model.RestResponseBuilder; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; /** * Handle components */ @Slf4j @RestController @RequestMapping({ "/rest/components", "/rest/v1/components", "/rest/latest/components" }) @Api(value = "", description = "Operations on Components") public class ComponentController { @Resource(name = "alien-es-dao") private IGenericSearchDAO dao; @Resource private IToscaTypeSearchService toscaTypeSearchService; @Resource private TagService tagService; /** * Get details for a component. * * @param id unique id of the component for which to get details. * @return A {@link RestResponse} that contains an {@link AbstractToscaType} . */ @ApiOperation(value = "Get details for a component (tosca type) from it's id (including archive hash).") @RequestMapping(value = "/{id:.+}", method = RequestMethod.GET) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER', 'COMPONENTS_BROWSER')") public RestResponse<AbstractToscaType> getComponent(@PathVariable String id, @RequestParam(required = false) QueryComponentType toscaType) { Class<? extends AbstractToscaType> queryClass = toscaType == null ? AbstractToscaType.class : toscaType.getIndexedToscaElementClass(); AbstractToscaType component = dao.findById(queryClass, id); return RestResponseBuilder.<AbstractToscaType> builder().data(component).build(); } /** * Get details for a component based on it's name and version. * * @param elementId unique id of the component for which to get details. * @param version unique id of the component for which to get details. * @return A {@link RestResponse} that contains an {@link AbstractToscaType} . */ @ApiOperation(value = "Get details for a component (tosca type) from it's id (including archive hash).") @RequestMapping(value = "/element/{elementId:.+}/version/{version:.+}", method = RequestMethod.GET) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER', 'COMPONENTS_BROWSER')") public RestResponse<AbstractToscaType> getComponent(@PathVariable String elementId, @PathVariable String version, @RequestParam(required = false) QueryComponentType toscaType) { Class<? extends AbstractToscaType> queryClass = toscaType == null ? AbstractToscaType.class : toscaType.getIndexedToscaElementClass(); AbstractToscaType component = toscaTypeSearchService.find(queryClass, elementId, version); return RestResponseBuilder.<AbstractToscaType> builder().data(component).build(); } /** * Get all versions of a given component. * * @param elementId unique id of the component for which to get all other versions. * @return A {@link RestResponse} that contains an {@link AbstractToscaType} . */ @ApiOperation(value = "Get details for a component (tosca type).") @RequestMapping(value = "/element/{elementId:.+}/versions", method = RequestMethod.GET) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER', 'COMPONENTS_BROWSER')") public RestResponse<CatalogVersionResult[]> getComponentVersions(@PathVariable String elementId, @RequestParam(required = false) QueryComponentType toscaType) { Class<? extends AbstractToscaType> queryClass = toscaType == null ? AbstractToscaType.class : toscaType.getIndexedToscaElementClass(); Object array = toscaTypeSearchService.findAll(queryClass, elementId); if (array != null) { int length = Array.getLength(array); CatalogVersionResult[] versions = new CatalogVersionResult[length]; for (int i = 0; i < length; i++) { AbstractToscaType element = ((AbstractToscaType) Array.get(array, i)); versions[i] = new CatalogVersionResult(element.getId(), element.getArchiveVersion()); } return RestResponseBuilder.<CatalogVersionResult[]> builder().data(versions).build(); } return RestResponseBuilder.<CatalogVersionResult[]> builder().data(new CatalogVersionResult[0]).build(); } @ApiOperation(value = "Get details for a component (tosca type).") @RequestMapping(value = "/getInArchives", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER', 'COMPONENTS_BROWSER')") public RestResponse<AbstractToscaType> getComponent(@RequestBody ElementFromArchiveRequest checkElementExistRequest) throws ClassNotFoundException { Class<? extends AbstractToscaType> elementClass = checkElementExistRequest.getComponentType().getIndexedToscaElementClass(); AbstractToscaType element = toscaTypeSearchService.getElementInDependencies(elementClass, checkElementExistRequest.getElementName(), checkElementExistRequest.getDependencies()); return RestResponseBuilder.<AbstractToscaType> builder().data(element).build(); } /** * Check if an element exist in alien repository * * @param checkElementExistRequest request * @return A rest response that contains true if the element has been found in Alien repository, and false if not. * @throws ClassNotFoundException if the class of element is not available on server */ @ApiOperation(value = "Verify that a component (tosca element) exists in alien's repository.") @RequestMapping(value = "/exist", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER', 'COMPONENTS_BROWSER')") public RestResponse<Boolean> checkElementExist(@RequestBody ElementFromArchiveRequest checkElementExistRequest) throws ClassNotFoundException { Class<? extends AbstractToscaType> elementClass = checkElementExistRequest.getComponentType().getIndexedToscaElementClass(); Boolean found = toscaTypeSearchService.isElementExistInDependencies(elementClass, checkElementExistRequest.getElementName(), checkElementExistRequest.getDependencies()); return RestResponseBuilder.<Boolean> builder().data(found).build(); } /** * Search for TOSCA elements. * * @param searchRequest The search request. * @return A {@link RestResponse} that contains a {@link FacetedSearchResult} of {@link NodeType}. */ @ApiOperation(value = "Search for components (tosca types) in alien.") @RequestMapping(value = "/search", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER', 'COMPONENTS_BROWSER')") public RestResponse<FacetedSearchResult<? extends AbstractToscaType>> search(@RequestBody SearchRequest searchRequest) { Class<? extends AbstractToscaType> queryClass = searchRequest.getType() == null ? AbstractToscaType.class : searchRequest.getType().getIndexedToscaElementClass(); FacetedSearchResult<? extends AbstractToscaType> searchResult = toscaTypeSearchService.search(queryClass, searchRequest.getQuery(), searchRequest.getSize(), searchRequest.getFilters()); return RestResponseBuilder.<FacetedSearchResult<? extends AbstractToscaType>> builder().data(searchResult).build(); } /** * Get the component recommended as default for a capability * * @param capability * @return A {@link RestResponse} that contains an {@link NodeType} . */ @ApiOperation(value = "Get details for an indexed node type..") @RequestMapping(value = "/recommendation/{capability:.+}", method = RequestMethod.GET) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER', 'COMPONENTS_BROWSER')") public RestResponse<NodeType> getRecommendedForCapability(@PathVariable String capability) { NodeType component = getDefaultNodeForCapability(capability); return RestResponseBuilder.<NodeType> builder().data(component).build(); } /** * define a component as default recommended for a specific capability. * Only one component can be recommended as default for a capability. * * @param recommendationRequest : {@link RecommendationRequest} object mapping the request body of the REST call. * @return A {@link RestResponse} that contains the component {@link NodeType} that has just been recommended. */ @ApiOperation(value = "Set the given node type as default for the given capability.") @RequestMapping(value = "/recommendation", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER')") @Audit public RestResponse<NodeType> recommendComponentForCapability(@RequestBody RecommendationRequest recommendationRequest) { removeFromDefaultCapabilities(recommendationRequest.getCapability()); NodeType component = dao.findById(NodeType.class, recommendationRequest.getComponentId()); if (component != null) { if (component.getDefaultCapabilities() == null) { component.setDefaultCapabilities(new ArrayList<String>()); } component.getDefaultCapabilities().add(recommendationRequest.getCapability()); log.info("Defining the component <" + component.getId() + "> as default for the capability <" + recommendationRequest.getCapability() + ">."); dao.save(component); } return RestResponseBuilder.<NodeType> builder().data(component).build(); } /** * un-define a component as default recommended for a specific capability. * * @param recommendationRequest : {@link RecommendationRequest} object mapping the request body of the REST call. * @return A {@link RestResponse} that contains the component {@link NodeType} that has just been undefined as default. */ @ApiOperation(value = "Remove a recommendation for a node type.", notes = "If a node type is set as default for a given capability, you can remove this setting by calling this operation with the right request parameters.") @RequestMapping(value = "/unflag", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER')") @Audit public RestResponse<NodeType> unflagAsDefaultForCapability(@RequestBody RecommendationRequest recommendationRequest) { NodeType component = dao.findById(NodeType.class, recommendationRequest.getComponentId()); if (component != null && component.getDefaultCapabilities() != null) { component.getDefaultCapabilities().remove(recommendationRequest.getCapability()); log.info("Undefining the component <" + component.getId() + "> as default for the capability <" + recommendationRequest.getCapability() + ">."); dao.save(component); } return RestResponseBuilder.<NodeType> builder().data(component).build(); } /** * Update or insert one tag for a given component * * @param componentId The if of the component for which to insert a tag. * @param updateTagRequest The request that contains the key and value for the tag to update. * @return a void rest response that contains no data if successful and an error if something goes wrong. */ @ApiOperation(value = "Update or insert a tag for a component (tosca element).") @RequestMapping(value = "/{componentId:.+}/tags", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER')") @Audit public RestResponse<Void> upsertTag(@PathVariable String componentId, @RequestBody UpdateTagRequest updateTagRequest) { RestError updateComponantTagError = null; NodeType component = dao.findById(NodeType.class, componentId); if (component != null) { tagService.upsertTag(component, updateTagRequest.getTagKey(), updateTagRequest.getTagValue()); } else { updateComponantTagError = RestErrorBuilder.builder(RestErrorCode.COMPONENT_MISSING_ERROR) .message("Tag update operation failed. Could not find component with id <" + componentId + ">.").build(); } return RestResponseBuilder.<Void> builder().error(updateComponantTagError).build(); } @ApiOperation(value = "Delete a tag for a component (tosca element).") @RequestMapping(value = "/{componentId:.+}/tags/{tagId}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('ADMIN', 'COMPONENTS_MANAGER')") @Audit public RestResponse<Void> deleteTag(@PathVariable String componentId, @PathVariable String tagId) { RestError deleteComponentTagError = null; NodeType component = dao.findById(NodeType.class, componentId); if (component != null) { tagService.removeTag(component, tagId); } else { deleteComponentTagError = RestErrorBuilder.builder(RestErrorCode.COMPONENT_MISSING_ERROR) .message("Tag delete operation failed. Could not find component with id <" + componentId + ">.").build(); } return RestResponseBuilder.<Void> builder().error(deleteComponentTagError).build(); } private void removeFromDefaultCapabilities(String capability) { NodeType component = getDefaultNodeForCapability(capability); if (component != null) { component.getDefaultCapabilities().remove(capability); dao.save(component); } } private NodeType getDefaultNodeForCapability(String capability) { Map<String, String[]> filters = new HashMap<>(); filters.put(Constants.DEFAULT_CAPABILITY_FIELD_NAME, new String[] { capability.toLowerCase() }); GetMultipleDataResult result = dao.find(NodeType.class, filters, 1); if (result == null || result.getData() == null || result.getData().length == 0) { return null; } return (NodeType) result.getData()[0]; } }