/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache license, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the license for the specific language governing permissions and * limitations under the license. */ package org.apache.logging.log4j.audit.service.controller; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Optional; import javax.annotation.PostConstruct; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.audit.service.catalog.AuditManager; import org.apache.logging.log4j.catalog.api.Attribute; import org.apache.logging.log4j.catalog.api.Category; import org.apache.logging.log4j.catalog.api.Event; import org.apache.logging.log4j.catalog.api.Product; import org.apache.logging.log4j.catalog.api.Versions; import org.apache.logging.log4j.catalog.jpa.converter.AttributeConverter; import org.apache.logging.log4j.catalog.jpa.converter.AttributeModelConverter; import org.apache.logging.log4j.catalog.jpa.converter.CategoryConverter; import org.apache.logging.log4j.catalog.jpa.converter.CategoryModelConverter; import org.apache.logging.log4j.catalog.jpa.converter.EventConverter; import org.apache.logging.log4j.catalog.jpa.converter.EventModelConverter; import org.apache.logging.log4j.catalog.jpa.converter.ProductConverter; import org.apache.logging.log4j.catalog.jpa.converter.ProductModelConverter; import org.apache.logging.log4j.catalog.jpa.model.AttributeModel; import org.apache.logging.log4j.catalog.jpa.model.CategoryModel; import org.apache.logging.log4j.catalog.jpa.model.EventModel; import org.apache.logging.log4j.catalog.jpa.model.ProductModel; import org.apache.logging.log4j.catalog.jpa.service.AttributeService; import org.apache.logging.log4j.catalog.jpa.service.CategoryService; import org.apache.logging.log4j.catalog.jpa.service.EventService; import org.apache.logging.log4j.catalog.jpa.service.ProductService; import org.modelmapper.ModelMapper; import org.modelmapper.TypeToken; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import static org.apache.logging.log4j.catalog.api.constant.Constants.DEFAULT_CATALOG; @RestController @RequestMapping(value = "/catalog") public class CatalogController { private static final Logger LOGGER = LogManager.getLogger(CatalogController.class); private final ModelMapper attributeModelMapper = new ModelMapper(); private final ModelMapper eventModelMapper = new ModelMapper(); private final ModelMapper productModelMapper = new ModelMapper(); private final ModelMapper categoryModelMapper = new ModelMapper(); @Autowired private AttributeService attributeService; @Autowired private AttributeModelConverter attributeModelConverter; @Autowired private AttributeConverter attributeConverter; @Autowired private EventService eventService; @Autowired private EventModelConverter eventModelConverter; @Autowired private EventConverter eventConverter; @Autowired private ProductService productService; @Autowired private ProductModelConverter productModelConverter; @Autowired private ProductConverter productConverter; @Autowired private CategoryService categoryService; @Autowired private AuditManager auditManager; @Autowired private CategoryModelConverter categoryModelConverter; @Autowired private CategoryConverter categoryConverter; @PostConstruct public void init() { attributeModelMapper.addConverter(attributeModelConverter); eventModelMapper.addConverter(eventModelConverter); productModelMapper.addConverter(productModelConverter); categoryModelMapper.addConverter(categoryModelConverter); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "List catalog Attributes", notes = "List catalog attributes for a catalog id", tags = {"Catalog"}) @GetMapping(value = "{catalogId}/attributes") public ResponseEntity<List<Attribute>> getAttributes(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId, @RequestParam(value = "startIndex", required = false) Integer startIndex, @RequestParam(value = "pageSize", required = false) Integer pageSize, @RequestParam(value = "sortCol", required= false) String sortColumn, @RequestParam(value = "sortDir", required = false) String sortDirection) { Type listType = new TypeToken<List<Attribute>>() { }.getType(); List<Attribute> attributes; if (startIndex == null || pageSize == null) { attributes = attributeModelMapper.map(attributeService.getAttributes(catalogId), listType); } else { sortDirection = validateSortDirection(sortDirection); if (sortColumn == null || sortColumn.length() == 0) { sortColumn = "name"; } int startPage = 0; if (startIndex > 0) { startPage = startIndex / pageSize; } attributes = attributeModelMapper.map(attributeService.getAttributes(startPage, pageSize, sortColumn, sortDirection), listType); } if (attributes == null) { attributes = new ArrayList<>(); } return new ResponseEntity<>(attributes, HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Create a catalog Attribute", notes = "Returns a catalog attribute", tags = {"Catalog"}) @GetMapping(value = "{catalogId}/attribute/{name}") public ResponseEntity<Attribute> getAttribute(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId, @ApiParam(value = "attribute name", required = true) @PathVariable String name) { Optional<AttributeModel> optional = attributeService.getAttribute(catalogId, name); if (!optional.isPresent()) { LOGGER.warn("Unable to locate attribute {} in catalog {}", name, catalogId); return new ResponseEntity<>(HttpStatus.NOT_FOUND); } Attribute attribute = attributeModelConverter.convert(optional.get()); return new ResponseEntity<>(attribute, HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Create a catalog Attribute", notes = "Creates a catalog attribute", tags = {"Catalog"}) @PostMapping(value = "/attribute", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Attribute> createAttribute(@ApiParam(value = "attribute", required = true) @RequestBody Attribute attribute) { if (attribute.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required."); } if (DEFAULT_CATALOG.equals(attribute.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } AttributeModel model; synchronized(this) { Optional<AttributeModel> opt = attributeService.getAttribute(attribute.getCatalogId(), attribute.getName()); if (opt != null && opt.isPresent()) { throw new IllegalStateException( "An attribute named " + attribute.getName() + " in catalog " + attribute.getCatalogId() + " already exists"); } model = attributeService.saveAttribute(attributeConverter.convert(attribute)); auditManager.saveAttribute(attribute); } return new ResponseEntity<>(attributeModelConverter.convert(model), HttpStatus.CREATED); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Update a catalog Attribute", notes = "Updates a catalog attribute", tags = {"Catalog"}) @PutMapping(value = "/attribute", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Attribute> updateAttribute(@ApiParam(value = "attribute", required = true) @RequestBody Attribute attribute) { if (attribute.getId() == null) { throw new IllegalArgumentException("An Attribute must have an id to be updated."); } if (attribute.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required in the Attribute."); } if (DEFAULT_CATALOG.equals(attribute.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } AttributeModel model = attributeConverter.convert(attribute); model = attributeService.saveAttribute(model); auditManager.saveAttribute(attribute); return new ResponseEntity<>(attributeModelConverter.convert(model), HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Deletes a catalog Attribute", notes = "Deletes a catalog attribute", tags = {"Catalog"}) @DeleteMapping(value = "/attribute/{id}") public ResponseEntity<?> deleteAttribute(@RequestParam("id") Long attributeId) { synchronized (this) { Optional<AttributeModel> opt = attributeService.getAttribute(attributeId); if (opt.isPresent()) { attributeService.deleteAttribute(attributeId); } } return new ResponseEntity<>(HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "List catalog Events", notes = "Lists catalog events for a catalog id", tags = {"Catalog"}) @GetMapping(value = "{catalogId}/events") public ResponseEntity<List<Event>> getEvents(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId, @RequestParam(value = "startIndex", required = false) Integer startIndex, @RequestParam(value = "pageSize", required = false) Integer pageSize, @RequestParam(value = "sortCol", required= false) String sortColumn, @RequestParam(value = "sortDir", required = false) String sortDirection) { Type listType = new TypeToken<List<Event>>() {}.getType(); List<Event> events; if (startIndex == null || pageSize == null) { events = eventModelMapper.map(eventService.getEvents(catalogId), listType); } else { sortDirection = validateSortDirection(sortDirection); if (sortColumn == null || sortColumn.length() == 0) { sortColumn = "name"; } int startPage = 0; if (startIndex > 0) { startPage = startIndex / pageSize; } events = eventModelMapper.map(eventService.getEvents(startPage, pageSize, sortColumn, sortDirection), listType); } if (events == null) { events = new ArrayList<>(); } return new ResponseEntity<>(events, HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Create a catalog Event", notes = "Creates a catalog event", tags = {"Catalog"}) @PostMapping(value = "/event", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Event> createEvent(@ApiParam(value = "event", required = true) @RequestBody Event event) { if (event.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required to create an event."); } if (DEFAULT_CATALOG.equals(event.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } EventModel model; synchronized(this) { Optional<EventModel> opt = eventService.getEvent(event.getCatalogId(), event.getName()); if (opt != null && opt.isPresent()) { throw new IllegalStateException( "An event named " + event.getName() + " in catalog " + event.getCatalogId() + " already exists"); } model = auditManager.saveEvent(event); } return new ResponseEntity<>(eventModelConverter.convert(model), HttpStatus.CREATED); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Update a catalog Event", notes = "Updates a catalog event", tags = {"Catalog"}) @PutMapping(value = "/event", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Event> updateEvent(@ApiParam(value = "event", required = true) @RequestBody Event event) { if (event.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required to update an event."); } if (DEFAULT_CATALOG.equals(event.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } EventModel model; synchronized(this) { model = eventConverter.convert(event); model = eventService.saveEvent(model); } return new ResponseEntity<>(eventModelConverter.convert(model), HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Deletes a catalog event", notes = "Deletes a catalog event", tags = {"Catalog"}) @DeleteMapping(value = "/event/{id}") public ResponseEntity<?> deleteEvent(@RequestParam("id") Long eventId) { eventService.deleteEvent(eventId); return new ResponseEntity<>(HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "List catalog Products", notes = "Lists catalog products for a catalog id", tags = {"Catalog"}) @GetMapping(value = "{catalogId}/products") public ResponseEntity<List<Product>> getProducts(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId, @RequestParam(value = "startIndex", required = false) Integer startIndex, @RequestParam(value = "pageSize", required = false) Integer pageSize, @RequestParam(value = "sortCol", required= false) String sortColumn, @RequestParam(value = "sortDir", required = false) String sortDirection) { Type listType = new TypeToken<List<Product>>() {}.getType(); List<Product> products; if (startIndex == null || pageSize == null) { products = productModelMapper.map(productService.getProducts(catalogId), listType); } else { sortDirection = validateSortDirection(sortDirection); if (sortColumn == null || sortColumn.length() == 0) { sortColumn = "name"; } int startPage = 0; if (startIndex > 0) { startPage = startIndex / pageSize; } products = productModelMapper.map(productService.getProducts(startPage, pageSize, sortColumn, sortDirection), listType); } if (products == null) { products = new ArrayList<>(); } return new ResponseEntity<>(products, HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Create a catalog Product", notes = "Creates a catalog product", tags = {"Catalog"}) @PostMapping(value = "/product", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Product> createProduct(@ApiParam(value = "product", required = true) @RequestBody Product product) { if (product.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required to create a product."); } if (DEFAULT_CATALOG.equals(product.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } Optional<ProductModel> opt = productService.getProduct(product.getCatalogId(), product.getName()); if (opt != null && opt.isPresent()) { throw new IllegalStateException("A product named "+ product.getName() + " in catalog " + product.getCatalogId() + " already exists"); } ProductModel model = productConverter.convert(product); model = productService.saveProduct(model); return new ResponseEntity<>(productModelConverter.convert(model), HttpStatus.CREATED); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Update a catalog Product", notes = "Updates a catalog event", tags = {"Catalog"}) @PutMapping(value = "/product", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Product> updateProduct(@ApiParam(value = "product", required = true) @RequestBody Product product) { if (product.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required to update a product."); } if (DEFAULT_CATALOG.equals(product.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } ProductModel model = productConverter.convert(product); model = productService.saveProduct(model); return new ResponseEntity<>(productModelConverter.convert(model), HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Deletes a catalog product", notes = "Deletes a catalog product", tags = {"Catalog"}) @DeleteMapping(value = "/product/{id}") public ResponseEntity<?> deleteProduct(@RequestParam("id") Long productId) { productService.deleteProduct(productId); return new ResponseEntity<>(HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "List catalog Categories", notes = "Lists catalog categories for a catalog id", tags = {"Catalog"}) @GetMapping(value = "{catalogId}/categories") public ResponseEntity<List<Category>> getCategories(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId, @RequestParam(value = "startIndex", required = false) Integer startIndex, @RequestParam(value = "pageSize", required = false) Integer pageSize, @RequestParam(value = "sortCol", required= false) String sortColumn, @RequestParam(value = "sortDir", required = false) String sortDirection) { Type listType = new TypeToken<List<Category>>() {}.getType(); List<Category> categories; if (startIndex == null || pageSize == null) { categories = categoryModelMapper.map(categoryService.getCategories(catalogId), listType); } else { sortDirection = validateSortDirection(sortDirection); if (sortColumn == null || sortColumn.length() == 0) { sortColumn = "name"; } int startPage = 0; if (startIndex > 0) { startPage = startIndex / pageSize; } categories = categoryModelMapper.map(categoryService.getCategories(startPage, pageSize, sortColumn, sortDirection), listType); } if (categories == null) { categories = new ArrayList<>(); } return new ResponseEntity<>(categories, HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Create a catalog Category", notes = "Creates a catalog category", tags = {"Catalog"}) @PostMapping(value = "/category", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Category> createCategory(@ApiParam(value = "category", required = true) @RequestBody Category category) { if (category.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required to create a category."); } if (DEFAULT_CATALOG.equals(category.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } Optional<CategoryModel> opt = categoryService.getCategory(category.getCatalogId(), category.getName()); if (opt != null && opt.isPresent()) { throw new IllegalStateException("A category named "+ category.getName() + " in catalog " + category.getCatalogId() + " already exists"); } CategoryModel model = categoryConverter.convert(category); model = categoryService.saveCategory(model); return new ResponseEntity<>(categoryModelConverter.convert(model), HttpStatus.CREATED); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Update a catalog Category", notes = "Updates a catalog category", tags = {"Catalog"}) @PutMapping(value = "/category", consumes=Versions.V1_0, produces=Versions.V1_0) public ResponseEntity<Category> updateCategory(@ApiParam(value = "category", required = true) @RequestBody Category category) { if (category.getCatalogId() == null) { throw new IllegalArgumentException("A catalog id is required to create a category."); } if (DEFAULT_CATALOG.equals(category.getCatalogId())) { throw new IllegalArgumentException("The default catalog cannot be modified at run time."); } CategoryModel model = categoryConverter.convert(category); model = categoryService.saveCategory(model); return new ResponseEntity<>(categoryModelConverter.convert(model), HttpStatus.OK); } @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")}) @ApiOperation(value = "Deletes a catalog category", notes = "Deletes a catalog category", tags = {"Catalog"}) @DeleteMapping(value = "/category/{id}") public ResponseEntity<?> deleteCategory(@RequestParam("id") Long categoryId) { categoryService.deleteCategory(categoryId); return new ResponseEntity<>(HttpStatus.OK); } private String validateSortDirection(String sortDirection) { if (sortDirection == null) { sortDirection = "ASC"; } else if (!sortDirection.equals("ASC") && !sortDirection.equals("DESC")) { LOGGER.warn("Invalid sort direction {}, defaulting to ascending", sortDirection); sortDirection = "ASC"; } return sortDirection; } }