View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.audit.service.controller;
18  
19  import java.lang.reflect.Type;
20  import java.util.ArrayList;
21  import java.util.List;
22  import java.util.Optional;
23  import javax.annotation.PostConstruct;
24  
25  import io.swagger.annotations.ApiImplicitParam;
26  import io.swagger.annotations.ApiImplicitParams;
27  import io.swagger.annotations.ApiOperation;
28  import io.swagger.annotations.ApiParam;
29  import org.apache.logging.log4j.LogManager;
30  import org.apache.logging.log4j.Logger;
31  import org.apache.logging.log4j.audit.service.catalog.AuditManager;
32  import org.apache.logging.log4j.catalog.api.Attribute;
33  import org.apache.logging.log4j.catalog.api.Category;
34  import org.apache.logging.log4j.catalog.api.Event;
35  import org.apache.logging.log4j.catalog.api.Product;
36  import org.apache.logging.log4j.catalog.api.Versions;
37  import org.apache.logging.log4j.catalog.jpa.converter.AttributeConverter;
38  import org.apache.logging.log4j.catalog.jpa.converter.AttributeModelConverter;
39  import org.apache.logging.log4j.catalog.jpa.converter.CategoryConverter;
40  import org.apache.logging.log4j.catalog.jpa.converter.CategoryModelConverter;
41  import org.apache.logging.log4j.catalog.jpa.converter.EventConverter;
42  import org.apache.logging.log4j.catalog.jpa.converter.EventModelConverter;
43  import org.apache.logging.log4j.catalog.jpa.converter.ProductConverter;
44  import org.apache.logging.log4j.catalog.jpa.converter.ProductModelConverter;
45  import org.apache.logging.log4j.catalog.jpa.model.AttributeModel;
46  import org.apache.logging.log4j.catalog.jpa.model.CategoryModel;
47  import org.apache.logging.log4j.catalog.jpa.model.EventModel;
48  import org.apache.logging.log4j.catalog.jpa.model.ProductModel;
49  import org.apache.logging.log4j.catalog.jpa.service.AttributeService;
50  import org.apache.logging.log4j.catalog.jpa.service.CategoryService;
51  import org.apache.logging.log4j.catalog.jpa.service.EventService;
52  import org.apache.logging.log4j.catalog.jpa.service.ProductService;
53  import org.modelmapper.ModelMapper;
54  import org.modelmapper.TypeToken;
55  import org.springframework.beans.factory.annotation.Autowired;
56  import org.springframework.http.HttpStatus;
57  import org.springframework.http.ResponseEntity;
58  import org.springframework.web.bind.annotation.DeleteMapping;
59  import org.springframework.web.bind.annotation.GetMapping;
60  import org.springframework.web.bind.annotation.PathVariable;
61  import org.springframework.web.bind.annotation.PostMapping;
62  import org.springframework.web.bind.annotation.PutMapping;
63  import org.springframework.web.bind.annotation.RequestBody;
64  import org.springframework.web.bind.annotation.RequestMapping;
65  import org.springframework.web.bind.annotation.RequestParam;
66  import org.springframework.web.bind.annotation.RestController;
67  
68  import static org.apache.logging.log4j.catalog.api.constant.Constants.DEFAULT_CATALOG;
69  
70  @RestController
71  @RequestMapping(value = "/catalog")
72  public class CatalogController {
73  
74      private static final Logger LOGGER = LogManager.getLogger(CatalogController.class);
75  
76      private ModelMapper attributeModelMapper = new ModelMapper();
77      private ModelMapper eventModelMapper = new ModelMapper();
78      private ModelMapper productModelMapper = new ModelMapper();
79      private ModelMapper categoryModelMapper = new ModelMapper();
80  
81      @Autowired
82      private AttributeService attributeService;
83  
84      @Autowired
85      private AttributeModelConverter attributeModelConverter;
86  
87      @Autowired
88      private AttributeConverter attributeConverter;
89  
90      @Autowired
91      private EventService eventService;
92  
93      @Autowired
94      private EventModelConverter eventModelConverter;
95  
96      @Autowired
97      private EventConverter eventConverter;
98  
99      @Autowired
100     private ProductService productService;
101 
102     @Autowired
103     private ProductModelConverter productModelConverter;
104 
105     @Autowired
106     private ProductConverter productConverter;
107 
108     @Autowired
109     private CategoryService categoryService;
110 
111     @Autowired
112     private AuditManager auditManager;
113 
114     @Autowired
115     private CategoryModelConverter categoryModelConverter;
116 
117     @Autowired
118     private CategoryConverter categoryConverter;
119 
120     @PostConstruct
121     public void init() {
122         attributeModelMapper.addConverter(attributeModelConverter);
123         eventModelMapper.addConverter(eventModelConverter);
124         productModelMapper.addConverter(productModelConverter);
125         categoryModelMapper.addConverter(categoryModelConverter);
126     }
127 
128     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
129     @ApiOperation(value = "List catalog Attributes", notes = "List catalog attributes for a catalog id", tags = {"Catalog"})
130     @GetMapping(value = "{catalogId}/attributes")
131     public ResponseEntity<List<Attribute>> getAttributes(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId,
132                                                          @RequestParam(value = "startIndex", required = false) Integer startIndex,
133                                                          @RequestParam(value = "pageSize", required = false) Integer pageSize,
134                                                          @RequestParam(value = "sortCol", required= false) String sortColumn,
135                                                          @RequestParam(value = "sortDir", required = false) String sortDirection) {
136         Type listType = new TypeToken<List<Attribute>>() {
137         }.getType();
138         List<Attribute> attributes = null;
139         if (startIndex == null || pageSize == null) {
140             attributes = attributeModelMapper.map(attributeService.getAttributes(catalogId), listType);
141         } else {
142             sortDirection = validateSortDirection(sortDirection);
143             if (sortColumn == null || sortColumn.length() == 0) {
144                 sortColumn = "name";
145             }
146             int startPage = 0;
147             if (startIndex > 0) {
148                 startPage = startIndex / pageSize;
149             }
150             attributes = attributeModelMapper.map(attributeService.getAttributes(startPage, pageSize, sortColumn,
151                     sortDirection), listType);
152         }
153         if (attributes == null) {
154             attributes = new ArrayList<>();
155         }
156         return new ResponseEntity<>(attributes, HttpStatus.OK);
157     }
158 
159     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
160     @ApiOperation(value = "Create a catalog Attribute", notes = "Returns a catalog attribute", tags = {"Catalog"})
161     @GetMapping(value = "{catalogId}/attribute/{name}")
162     public ResponseEntity<Attribute> getAttribute(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId,
163                                                          @ApiParam(value = "attribute name", required = true) @PathVariable String name) {
164         Optional<AttributeModel> optional = attributeService.getAttribute(catalogId, name);
165         if (!optional.isPresent()) {
166             LOGGER.warn("Unable to locate attribute {} in catalog {}", name, catalogId);
167             return new ResponseEntity<>(HttpStatus.NOT_FOUND);
168         }
169         Attribute attribute = attributeModelConverter.convert(optional.get());
170         return new ResponseEntity<>(attribute, HttpStatus.OK);
171     }
172 
173 
174 
175     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
176     @ApiOperation(value = "Create a catalog Attribute", notes = "Creates a catalog attribute", tags = {"Catalog"})
177     @PostMapping(value = "/attribute", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
178     public ResponseEntity<Attribute> createAttribute(@ApiParam(value = "attribute", required = true) @RequestBody Attribute attribute) {
179         if (attribute.getCatalogId() == null) {
180             throw new IllegalArgumentException("A catalog id is required.");
181         }
182         if (DEFAULT_CATALOG.equals(attribute.getCatalogId())) {
183             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
184         }
185         AttributeModel model;
186         synchronized(this) {
187             Optional<AttributeModel> opt = attributeService.getAttribute(attribute.getCatalogId(), attribute.getName());
188             if (opt != null && opt.isPresent()) {
189                 throw new IllegalStateException(
190                     "An attribute named " + attribute.getName() + " in catalog " + attribute.getCatalogId() + " already exists");
191             }
192             model = attributeService.saveAttribute(attributeConverter.convert(attribute));
193             auditManager.saveAttribute(attribute);
194         }
195         return new ResponseEntity<>(attributeModelConverter.convert(model), HttpStatus.CREATED);
196     }
197 
198     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
199     @ApiOperation(value = "Update a catalog Attribute", notes = "Updates a catalog attribute", tags = {"Catalog"})
200     @PutMapping(value = "/attribute", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
201     public ResponseEntity<Attribute> updateAttribute(@ApiParam(value = "attribute", required = true) @RequestBody Attribute attribute) {
202         if (attribute.getId() == null) {
203             throw new IllegalArgumentException("An Attribute must have an id to be updated.");
204         }
205         if (attribute.getCatalogId() == null) {
206             throw new IllegalArgumentException("A catalog id is required in the Attribute.");
207         }
208         if (DEFAULT_CATALOG.equals(attribute.getCatalogId())) {
209             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
210         }
211         AttributeModel model = attributeConverter.convert(attribute);
212         model = attributeService.saveAttribute(model);
213         auditManager.saveAttribute(attribute);
214         return new ResponseEntity<>(attributeModelConverter.convert(model), HttpStatus.OK);
215     }
216 
217     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
218     @ApiOperation(value = "Deletes a catalog Attribute", notes = "Deletes a catalog attribute", tags = {"Catalog"})
219     @DeleteMapping(value = "/attribute/{id}")
220     public ResponseEntity<?> deleteAttribute(@RequestParam("id") Long attributeId) {
221         synchronized (this) {
222             Optional<AttributeModel> opt = attributeService.getAttribute(attributeId);
223             if (opt.isPresent()) {
224                 attributeService.deleteAttribute(attributeId);
225             }
226         }
227         return new ResponseEntity<>(HttpStatus.OK);
228     }
229 
230     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
231     @ApiOperation(value = "List catalog Events", notes = "Lists catalog events for a catalog id", tags = {"Catalog"})
232     @GetMapping(value = "{catalogId}/events")
233     public ResponseEntity<List<Event>> getEvents(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId,
234                                                          @RequestParam(value = "startIndex", required = false) Integer startIndex,
235                                                          @RequestParam(value = "pageSize", required = false) Integer pageSize,
236                                                          @RequestParam(value = "sortCol", required= false) String sortColumn,
237                                                          @RequestParam(value = "sortDir", required = false) String sortDirection) {
238         Type listType = new TypeToken<List<Event>>() {}.getType();
239         List<Event> events = null;
240         if (startIndex == null || pageSize == null) {
241             events = eventModelMapper.map(eventService.getEvents(catalogId), listType);
242         } else {
243             sortDirection = validateSortDirection(sortDirection);
244             if (sortColumn == null || sortColumn.length() == 0) {
245                 sortColumn = "name";
246             }
247             int startPage = 0;
248             if (startIndex > 0) {
249                 startPage = startIndex / pageSize;
250             }
251             events = eventModelMapper.map(eventService.getEvents(startPage, pageSize, sortColumn,
252                     sortDirection), listType);
253         }
254         if (events == null) {
255             events = new ArrayList<>();
256         }
257         return new ResponseEntity<>(events, HttpStatus.OK);
258     }
259 
260     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
261     @ApiOperation(value = "Create a catalog Event", notes = "Creates a catalog event", tags = {"Catalog"})
262     @PostMapping(value = "/event", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
263     public ResponseEntity<Event> createEvent(@ApiParam(value = "event", required = true) @RequestBody Event event) {
264         if (event.getCatalogId() == null) {
265             throw new IllegalArgumentException("A catalog id is required to create an event.");
266         }
267         if (DEFAULT_CATALOG.equals(event.getCatalogId())) {
268             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
269         }
270         EventModel model;
271         synchronized(this) {
272             Optional<EventModel> opt = eventService.getEvent(event.getCatalogId(), event.getName());
273             if (opt != null && opt.isPresent()) {
274                 throw new IllegalStateException(
275                     "An event named " + event.getName() + " in catalog " + event.getCatalogId() + " already exists");
276             }
277             model = auditManager.saveEvent(event);
278         }
279         return new ResponseEntity<>(eventModelConverter.convert(model), HttpStatus.CREATED);
280     }
281 
282     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
283     @ApiOperation(value = "Update a catalog Event", notes = "Updates a catalog event", tags = {"Catalog"})
284     @PutMapping(value = "/event", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
285     public ResponseEntity<Event> updateEvent(@ApiParam(value = "event", required = true) @RequestBody Event event) {
286         if (event.getCatalogId() == null) {
287             throw new IllegalArgumentException("A catalog id is required to update an event.");
288         }
289         if (DEFAULT_CATALOG.equals(event.getCatalogId())) {
290             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
291         }
292         EventModel model;
293         synchronized(this) {
294             model = eventConverter.convert(event);
295             model = eventService.saveEvent(model);
296         }
297         return new ResponseEntity<>(eventModelConverter.convert(model), HttpStatus.OK);
298     }
299 
300     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
301     @ApiOperation(value = "Deletes a catalog event", notes = "Deletes a catalog event", tags = {"Catalog"})
302     @DeleteMapping(value = "/event/{id}")
303     public ResponseEntity<?> deleteEvent(@RequestParam("id") Long eventId) {
304         eventService.deleteEvent(eventId);
305         return new ResponseEntity<>(HttpStatus.OK);
306     }
307 
308     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
309     @ApiOperation(value = "List catalog Products", notes = "Lists catalog products for a catalog id", tags = {"Catalog"})
310     @GetMapping(value = "{catalogId}/products")
311     public ResponseEntity<List<Product>> getProducts(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId,
312                                                  @RequestParam(value = "startIndex", required = false) Integer startIndex,
313                                                  @RequestParam(value = "pageSize", required = false) Integer pageSize,
314                                                  @RequestParam(value = "sortCol", required= false) String sortColumn,
315                                                  @RequestParam(value = "sortDir", required = false) String sortDirection) {
316         Type listType = new TypeToken<List<Product>>() {}.getType();
317         List<Product> products = null;
318         if (startIndex == null || pageSize == null) {
319             products = productModelMapper.map(productService.getProducts(catalogId), listType);
320         } else {
321             sortDirection = validateSortDirection(sortDirection);
322             if (sortColumn == null || sortColumn.length() == 0) {
323                 sortColumn = "name";
324             }
325             int startPage = 0;
326             if (startIndex > 0) {
327                 startPage = startIndex / pageSize;
328             }
329             products = productModelMapper.map(productService.getProducts(startPage, pageSize, sortColumn,
330                     sortDirection), listType);
331         }
332         if (products == null) {
333             products = new ArrayList<>();
334         }
335         return new ResponseEntity<>(products, HttpStatus.OK);
336     }
337 
338     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
339     @ApiOperation(value = "Create a catalog Product", notes = "Creates a catalog product", tags = {"Catalog"})
340     @PostMapping(value = "/product", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
341     public ResponseEntity<Product> createProduct(@ApiParam(value = "product", required = true) @RequestBody Product product) {
342         if (product.getCatalogId() == null) {
343             throw new IllegalArgumentException("A catalog id is required to create a product.");
344         }
345         if (DEFAULT_CATALOG.equals(product.getCatalogId())) {
346             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
347         }
348         Optional<ProductModel> opt = productService.getProduct(product.getCatalogId(), product.getName());
349         if (opt != null && opt.isPresent()) {
350             throw new IllegalStateException("A product named "+ product.getName() + " in catalog " +
351                     product.getCatalogId() + " already exists");
352         }
353         ProductModel model = productConverter.convert(product);
354         model = productService.saveProduct(model);
355         return new ResponseEntity<>(productModelConverter.convert(model), HttpStatus.CREATED);
356     }
357 
358     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
359     @ApiOperation(value = "Update a catalog Product", notes = "Updates a catalog event", tags = {"Catalog"})
360     @PutMapping(value = "/product", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
361     public ResponseEntity<Product> updateProduct(@ApiParam(value = "product", required = true) @RequestBody Product product) {
362         if (product.getCatalogId() == null) {
363             throw new IllegalArgumentException("A catalog id is required to update a product.");
364         }
365         if (DEFAULT_CATALOG.equals(product.getCatalogId())) {
366             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
367         }
368         ProductModel model = productConverter.convert(product);
369         model = productService.saveProduct(model);
370         return new ResponseEntity<>(productModelConverter.convert(model), HttpStatus.OK);
371     }
372 
373     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
374     @ApiOperation(value = "Deletes a catalog product", notes = "Deletes a catalog product", tags = {"Catalog"})
375     @DeleteMapping(value = "/product/{id}")
376     public ResponseEntity<?> deleteProduct(@RequestParam("id") Long productId) {
377         productService.deleteProduct(productId);
378         return new ResponseEntity<>(HttpStatus.OK);
379     }
380 
381     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
382     @ApiOperation(value = "List catalog Categories", notes = "Lists catalog categories for a catalog id", tags = {"Catalog"})
383     @GetMapping(value = "{catalogId}/categories")
384     public ResponseEntity<List<Category>> getCategories(@ApiParam(value = "catalog id", required = true) @PathVariable String catalogId,
385                                                      @RequestParam(value = "startIndex", required = false) Integer startIndex,
386                                                      @RequestParam(value = "pageSize", required = false) Integer pageSize,
387                                                      @RequestParam(value = "sortCol", required= false) String sortColumn,
388                                                      @RequestParam(value = "sortDir", required = false) String sortDirection) {
389         Type listType = new TypeToken<List<Category>>() {}.getType();
390         List<Category> categories = null;
391         if (startIndex == null || pageSize == null) {
392             categories = categoryModelMapper.map(categoryService.getCategories(catalogId), listType);
393         } else {
394             sortDirection = validateSortDirection(sortDirection);
395             if (sortColumn == null || sortColumn.length() == 0) {
396                 sortColumn = "name";
397             }
398             int startPage = 0;
399             if (startIndex > 0) {
400                 startPage = startIndex / pageSize;
401             }
402             categories = categoryModelMapper.map(categoryService.getCategories(startPage, pageSize, sortColumn,
403                     sortDirection), listType);
404         }
405         if (categories == null) {
406             categories = new ArrayList<>();
407         }
408         return new ResponseEntity<>(categories, HttpStatus.OK);
409     }
410 
411     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
412     @ApiOperation(value = "Create a catalog Category", notes = "Creates a catalog category", tags = {"Catalog"})
413     @PostMapping(value = "/category", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
414     public ResponseEntity<Category> createCategory(@ApiParam(value = "category", required = true) @RequestBody Category category) {
415         if (category.getCatalogId() == null) {
416             throw new IllegalArgumentException("A catalog id is required to create a category.");
417         }
418         if (DEFAULT_CATALOG.equals(category.getCatalogId())) {
419             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
420         }
421         Optional<CategoryModel> opt = categoryService.getCategory(category.getCatalogId(), category.getName());
422         if (opt != null && opt.isPresent()) {
423             throw new IllegalStateException("A category named "+ category.getName() + " in catalog " +
424                     category.getCatalogId() + " already exists");
425         }
426         CategoryModel model = categoryConverter.convert(category);
427         model = categoryService.saveCategory(model);
428         return new ResponseEntity<>(categoryModelConverter.convert(model), HttpStatus.CREATED);
429     }
430 
431     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
432     @ApiOperation(value = "Update a catalog Category", notes = "Updates a catalog category", tags = {"Catalog"})
433     @PutMapping(value = "/category", consumes=Versions.V1_0_VALUE, produces=Versions.V1_0_VALUE)
434     public ResponseEntity<Category> updateCategory(@ApiParam(value = "category", required = true) @RequestBody Category category) {
435         if (category.getCatalogId() == null) {
436             throw new IllegalArgumentException("A catalog id is required to create a category.");
437         }
438         if (DEFAULT_CATALOG.equals(category.getCatalogId())) {
439             throw new IllegalArgumentException("The default catalog cannot be modified at run time.");
440         }
441         CategoryModel model = categoryConverter.convert(category);
442         model = categoryService.saveCategory(model);
443         return new ResponseEntity<>(categoryModelConverter.convert(model), HttpStatus.OK);
444     }
445 
446     @ApiImplicitParams( {@ApiImplicitParam(dataType = "String", name = "Authorization", paramType = "header")})
447     @ApiOperation(value = "Deletes a catalog category", notes = "Deletes a catalog category", tags = {"Catalog"})
448     @DeleteMapping(value = "/category/{id}")
449     public ResponseEntity<?> deleteCategory(@RequestParam("id") Long categoryId) {
450         categoryService.deleteCategory(categoryId);
451         return new ResponseEntity<>(HttpStatus.OK);
452     }
453 
454     private String validateSortDirection(String sortDirection) {
455         if (sortDirection == null) {
456             sortDirection = "ASC";
457         } else if (sortDirection != "ASC" && sortDirection != "DESC") {
458             LOGGER.warn("Invalid sort direction {}, defaulting to ascending", sortDirection);
459             sortDirection = "ASC";
460         }
461         return sortDirection;
462     }
463 }