I have 2 GET endpoints for Chemical resource:
In the first endpoint, I want the chemical object by id, which is unique through every chemical.
#GetMapping("/chemical/{id}")
In the second GET endpoint, I want all the chemicals which are correspond to a specific lab (lab is mandatory).
#GetMapping("/chemical/{labKey}")
Spring cannot distinguish between /chemical/myLab and /chemical/12, I can understand this. I know I should change the endpoint mapping, but how? Can someone suggest a good convention for this specific case? eg. #GetMapping("/chemical/{labKey}/{id}" seems redundant for me, since I don't use labKey pathVariable, I will only need id, I will call something like this: chemicalService.findById(id).
I think the best way to do this is by using two end points:
/chemical/{id} which will fetch your chemical by identifier using #GetMapping("/chemicals/{id}").
The second would use a query param labKey to filter based on the lab key. This will have a generic #GetMapping("/chemicals"). chemicals?labKey=lab1 would be the URI.
Below is my implementation.
Controller class
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
#Service
public class ChemicalService {
private List<ChemicalDTO> chemicals = Arrays.asList(
new ChemicalDTO(1L, "chem1", "lab1"),
new ChemicalDTO(2L, "chem2", "lab2"),
new ChemicalDTO(3L, "chem3", "lab1"),
new ChemicalDTO(4L, "chem4", "lab3")
);
public Optional<ChemicalDTO> getChemicalById(Long id) {
return chemicals.stream().filter(e -> e.getId().equals(id)).findAny();
}
public List<ChemicalDTO> getChemicalByLabKey(String labKey) {
if( labKey.isBlank() || labKey.isEmpty() ) return new ArrayList<>();
return chemicals
.stream()
.filter(e -> e.getLabKey().equals(labKey))
.collect(Collectors.toList());
}
}
Service class
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
#Service
public class ChemicalService {
private List<ChemicalDTO> chemicals = Arrays.asList(
new ChemicalDTO(1L, "chem1", "lab1"),
new ChemicalDTO(2L, "chem2", "lab2"),
new ChemicalDTO(3L, "chem3", "lab1"),
new ChemicalDTO(4L, "chem4", "lab3")
);
public Optional<ChemicalDTO> getChemicalById(Long id) {
return chemicals.stream().filter(e -> e.getId().equals(id)).findAny();
}
public List<ChemicalDTO> getChemicalByLabKey(String labKey) {
if( labKey.isBlank() || labKey.isEmpty() ) return new ArrayList<>();
return chemicals
.stream()
.filter(e -> e.getLabKey().equals(labKey))
.collect(Collectors.toList());
}
}
DTO class
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public class ChemicalDTO {
private Long id;
private String name;
private String labKey;
}
You can find more information in the REST resource naming conventions and examples here
If the path is the same and the number of path variables is also the same, below would be the clean approach to handle this situation.
you can consider below code snippet
#GetMapping(value = "/chemical", params = "id")
public String getChemicalById(#RequestParam(value = "id") String id) {
return "ID method: "+id;
}
#GetMapping(value = "/chemical", params = "label")
public String getChemicalByLabel(#RequestParam(value = "label") String label) {
return "Label method: "+label;
}
The URLs are as follows:
/chemical?id=ID
/chemical?label=LABEL
Related
I am trying to insert data into the h2 database taking input from user.But primary key is getting inserted but the other is stored as null.
Here is my application.properties
spring.sql.init.platform==h2
spring.datasource.url=jdbc:h2:mem:preethi
spring.jpa.defer-datasource-initialization: true
spring.jpa.hibernate.ddl-auto=update
Here is Controller class AlienController.java
package com.preethi.springbootjpa.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.preethi.springbootjpa.model.Alien;
import com.preethi.springbootjpa.repo.AlienRepo;
#Controller
public class AlienController {
#Autowired
AlienRepo repo;
#RequestMapping("/")
public String home()
{
return "home.jsp";
}
#RequestMapping("/addAlien")
public String addAlien( Alien alien)
{
repo.save(alien);
return "home.jsp";
}
}
Here is Alien.java
package com.preethi.springbootjpa.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.springframework.stereotype.Component;
#Component
#Entity
public class Alien {
#Id
private int aid;
private String aname;
public Alien()
{
}
public Alien(int aid, String aname) {
super();
this.aid = aid;
this.aname = aname;
}
public int getAid() {
return aid;
}
public void setAid(int aid) {
this.aid = aid;
}
public String getName() {
return aname;
}
public void setName(String name) {
this.aname = name;
}
#Override
public String toString() {
return "Alien [aid=" + aid + ", name=" + aname + "]";
}
}
Here is AlienRepo.java
package com.preethi.springbootjpa.repo;
import org.springframework.data.repository.CrudRepository;
import com.preethi.springbootjpa.model.Alien;
public interface AlienRepo extends CrudRepository<Alien,Integer>{
}
Here is data.sql;
insert into alien values(101,'Preethi');
when I try to insert data from data.sql,it is getting inserted but when I try to insert data taking input from user, data is stored as null(except primary key).
Here is the table :
table
I got the same issue when i forgot to add the gettters and setters for an entity class.
Finally solved it, there is no problem with the code...It's just all the configuraton things that messed up.
Just did everything from scratch and took care of build path and configurations.
Solved..
Saurav Chaurasia
Fri, May 7, 5:00 PM (20 hours ago)
to me
Like I am able to use the static Policy enforcer by providing the path and method and resource into the application.properties. But in realtime application we will be having N number of roles and we will be having N number of API which we will be provide in the Resource, Policies and Permissions in KeyCloak. In Future if we want some more roles to be added into the keycloak and new resources will be added with necessary permission. We wont be coming back to our springboot to change the code for resource and roles. All the permission checking should be dynamic to the spring boot how can we do that please help me out.
The static one which I am using currently is working fine for few roles. We are adding more roles into the keyCloak.
Below is the Static code.
application.properties
server.port = 8090
keycloak.realm=university
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=external
keycloak.resource=course-management
keycloak.bearer-only=true
keycloak.credentials.secret=a5df9621-73c9-4e0e-9d7a-97e9c692a930
keycloak.securityConstraints[0].authRoles[0]=teacher
keycloak.securityConstraints[0].authRoles[1]=ta
keycloak.securityConstraints[0].authRoles[2]=student
keycloak.securityConstraints[0].authRoles[3]=parent
keycloak.securityConstraints[0].securityCollections[0].name=course managment
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /courses/get/*
#keycloak.policy-enforcer-config.lazy-load-paths=true
keycloak.policy-enforcer-config.paths[0].path=/courses/get/*
keycloak.policy-enforcer-config.paths[0].methods[0].method=GET
keycloak.policy-enforcer-config.paths[0].methods[0].scopes[0]=view
keycloak.policy-enforcer-config.paths[0].methods[1].method=DELETE
keycloak.policy-enforcer-config.paths[0].methods[1].scopes[0]=delete
Configuration.class
package com.lantana.school.course.coursemanagment.security;
`import java.util.List;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.representations.idm.authorization.Permission;
public class Identity {
private final KeycloakSecurityContext securityContext;
public Identity(KeycloakSecurityContext securityContext) {
this.securityContext = securityContext;
}
/**
* An example on how you can use the {#link org.keycloak.AuthorizationContext} to check for permissions granted by Keycloak for a particular user.
*
* #param name the name of the resource
* #return true if user has was granted with a permission for the given resource. Otherwise, false.
*/
public boolean hasResourcePermission(String name) {
System.out.println("Permission: "+getAuthorizationContext().hasResourcePermission(name));
return getAuthorizationContext().hasResourcePermission(name);
}
/**
* An example on how you can use {#link KeycloakSecurityContext} to obtain information about user's identity.
*
* #return the user name
*/
public String getName() {
System.out.println("UserName: "+securityContext.getIdToken().getPreferredUsername());
return securityContext.getIdToken().getPreferredUsername();
}
/**
* An example on how you can use the {#link org.keycloak.AuthorizationContext} to obtain all permissions granted for a particular user.
*
* #return
*/
public List<Permission> getPermissions() {
System.out.println("Permission 2: "+getAuthorizationContext().getPermissions());
return getAuthorizationContext().getPermissions();
}
/**
* Returns a {#link AuthorizationContext} instance holding all permissions granted for an user. The instance is build based on
* the permissions returned by Keycloak. For this particular application, we use the Entitlement API to obtain permissions for every single
* resource on the server.
*
* #return
*/
private AuthorizationContext getAuthorizationContext() {
System.out.println("getAuthorizationContext: "+ securityContext.getAuthorizationContext());
return securityContext.getAuthorizationContext();
}
}`
Controller.class
package com.lantana.school.course.coursemanagment.services;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.KeycloakSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityModel;
//import org.springframework.hateoas.Link;
//import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
//import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.lantana.school.course.coursemanagment.security.Identity;
#RestController
public class CourseController{
#Autowired
private HttpServletRequest request;
#Autowired
private CourseService couseService;
#Autowired
private hateo hatoeslink;
List<String> rol=new ArrayList<String>();
//
// #GetMapping(value = "/courses/api")
// public String generateApi(#RequestHeader("Authorization") String token){
////// rol.clear();
////// List headers = token.;
// System.out.println("Token: "+token);
////// System.out.println("Role Controller: "+rol);
////// rol=couseService.getRole(token);
////// List<?> link=new ArrayList<>();
//////
// return new String("Role Fetched");
// }
#GetMapping(value = "/courses/get/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public EntityModel<Course> getCourse(#PathVariable("id") long id, Model model,#RequestHeader("Authorization") String token) throws JsonProcessingException {
configCommonAttributes(model);
rol=couseService.getRole(token);
System.out.println("GetRole: "+rol);
Course course = couseService.getCourse(id);
hatoeslink.hateoLink(rol, id, model, token);
return EntityModel.of(course);
}
#DeleteMapping("/courses/delete/{id}")
public EntityModel<Course> deleteStudent(#PathVariable long id) {
System.out.println("calling delete operation");
//
Course course = couseService.getCourse(id);
couseService.deleteById(id);
return EntityModel.of(course);
}
#PostMapping("/courses")
public ResponseEntity<Course> createCourse(#RequestBody Course course) {
Course savedCourse = couseService.addCourse(course);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedCourse.getCode()).toUri();
return ResponseEntity.created(location).build();
}
private void configCommonAttributes(Model model) {
model.addAttribute("identity", new Identity(getKeycloakSecurityContext()));
}
private KeycloakSecurityContext getKeycloakSecurityContext() {
return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
}
}
Service.class
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
//import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.stereotype.Component;
#Component
public class CourseService {
public static final Map<Long, Course> courseMap = new LinkedHashMap<Long, Course>();
static {
Course cs2001 = new Course("CS2001", "Mathematical Foundations of Computing", "introduction", "term1");
Course cs2002 = new Course("CS2002", "Computer Organization and Systems", "introduction", "term1");
Course cs2003 = new Course("CS2003", "Data Management and Data Systems", "introduction", "term2");
Course cs2004 = new Course("CS2004", "Introduction to Computer Graphics and Imaging", "introduction", "term3");
Course cs2005 = new Course("CS2005", "Design and Analysis of Algorithms", "introduction", "term4");
Course cs2006 = new Course("CS2006", "Analysis of Networks", "introduction", "term4");
courseMap.put(cs2001.getId(), cs2001);
courseMap.put(cs2002.getId(), cs2002);
courseMap.put(cs2003.getId(), cs2003);
courseMap.put(cs2004.getId(), cs2004);
courseMap.put(cs2005.getId(), cs2005);
courseMap.put(cs2006.getId(), cs2006);
}
public Course getCourse(Long id) {
return courseMap.get(id);
}
public Course addCourse(Course course) {
courseMap.put(course.getId(), course);
return course;
}
public void deleteById(long id) {
courseMap.remove(id);
// return id+"Deleted Successfully";
}
public List<String> getRole(String token) {
String[] payload=token.split("\\.");
byte[] bytes = Base64.decodeBase64(payload[1]);
String decodedString = new String(bytes, StandardCharsets.UTF_8);
// System.out.println("Decoded: " + decodedString);
JSONObject jo=new JSONObject(decodedString);
JSONObject obj=jo.getJSONObject("realm_access");
JSONArray jArray=obj.getJSONArray("roles");
System.out.println(obj);
System.out.println(jArray);
List<String> role=new ArrayList();
for(int i=0;i<jArray.length();i++)
{ String ro=(String) jArray.get(i);
System.out.println(jArray.get(i));
role.add(ro);
}
// List action=new ArrayList();
// Map roles=((Map)jo.get("realm_access"));
// Iterator<Map.Entry> itr1=roles.entrySet().iterator();
// while(itr1.hasNext())
// {
// Map.Entry pair=itr1.next();
// System.out.println(pair);
// }
return role;
}
}
Model.class
package com.lantana.school.course.coursemanagment.services;
import org.springframework.hateoas.RepresentationModel;
public class Course extends RepresentationModel {
private static long nextID = 1000;
public Course(String code, String name, String modules, String enrollmentTerm) {
super();
this.id = nextID++;
this.code = code;
this.name = name;
this.modules = modules;
this.enrollmentTerm = enrollmentTerm;
}
Long id;
String code;
String name;
String modules;
String enrollmentTerm;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getModules() {
return modules;
}
public void setModules(String modules) {
this.modules = modules;
}
public String getEnrollmentTerm() {
return enrollmentTerm;
}
public void setEnrollmentTerm(String enrollmentTerm) {
this.enrollmentTerm = enrollmentTerm;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
hateos Custom class
package com.lantana.school.course.coursemanagment.services;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.stereotype.Component;
import org.springframework.ui.Model;
import com.fasterxml.jackson.core.JsonProcessingException;
#Component
public class hateo {
#Autowired
private CourseService couseService;
public EntityModel<Course> hateoLink(List<String> role,long id,Model model,String token)
{ Course course = couseService.getCourse(id);
course.removeLinks();
role.stream().forEach(action ->{
if(action.equalsIgnoreCase("teacher"))
{
// course.removeLinks();
course.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(CourseController.class).createCourse(course)).withRel("add"));
}
if(action.equalsIgnoreCase("student"))
{
try {
course.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(CourseController.class).getCourse(id, model,token)).withRel("view"));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(action.equalsIgnoreCase("parent"))
{
course.add(WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(CourseController.class).deleteStudent(id)).withRel("delete"));
}
});
return EntityModel.of(course);
}
}
If you are using Keycloak's Authorization Services, (which you are if you're using PEP), you shouldn't have to define roles in your spring boot keycloak-configuration. Notice how the roles aren't part of the policy-enforcer-config. If you just remove keycloak.securityConstraints[0].authRoles, and check for roles in your Policies over at the Keycloak server instead, you should be good.
As for the resource paths, I see that you have commented out keycloak.policy-enforcer-config.lazy-load-paths. With this together with http-method-as-scope, you shouldn't have to provide any additional configuration regarding your resources since the Keycloak-adapter will automatically grab that from annotations like #PostMapping("/courses"). (You would have to name your scopes in Keycloak after HTTP-methods for this to work). In this case, you're really using the default PEP-configuration and shouldn't have to specify anything else than enabling policy enforcing for your application.
Good afternoon,
I have a restController and I want to create an annotation that allows or not to execute a method based on an a custom header value.
If custom header tag equals something then the method must execute, if the custom header dont match, the method musth not execute
I have followed several articles but I have not been able.
I attached the code I created:
Annotation Code:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ApiVersion {
int[] value();
}
ApiVersionRequestMappingHandlerMapping
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private final String prefix;
public ApiVersionRequestMappingHandlerMapping(String prefix) {
this.prefix = prefix;
}
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if(info == null) return null;
ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);
if(methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
// Concatenate our ApiVersion with the usual request mapping
info = createApiVersionInfo(methodAnnotation, methodCondition).combine(info);
} else {
ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
if(typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
// Concatenate our ApiVersion with the usual request mapping
info = createApiVersionInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition<?> customCondition) {
int[] values = annotation.value();
String[] patterns = new String[values.length];
for(int i=0; i<values.length; i++) {
// Build the URL prefix
patterns[i] = prefix+values[i];
}
return new RequestMappingInfo(
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()),
new RequestMethodsRequestCondition(),
new ParamsRequestCondition(),
new HeadersRequestCondition(),
new ConsumesRequestCondition(),
new ProducesRequestCondition(),
customCondition);
}
}
Rest Controller
#RestController
#RequiredArgsConstructor
#RequestMapping("/api/example")
public class ExampleController {
private final UserService userService;
#ApiVersion (1)
#GetMapping("/myMethod")
public String myMethod(#AuthenticationPrincipal UserAuthenticatedDetails userAuthenticated) {
return userAuthenticated.getUsername();
}
}
ApiConfig
package xx.package.sample;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#ComponentScan("xx.package")
#Configuration
#EnableTransactionManagement
#EntityScan("xx.package.domain.entity")
#EnableJpaRepositories("xx.package.domain.repository")
#EnableAutoConfiguration
public class ApiConfig {
}
I know I'm missing something but I can't see what.
Regards, and thank you very much!
You could use #GetMapping(path = "/myMethod", headers = "My-Header=myValue").
a sequence of "My-Header=myValue" style expressions, with a request
only mapped if each such header is found to have the given value
see https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html#headers--
I am new to Spring and Spring Boot and am working through a book that is full of missing information.
I have a taco class:
public class Taco {
...
#Size(min=1, message="You must choose at least 1 ingredient")
private List<Ingredient> ingredients;
...
}
As you can see ingredients is of type List<Ingredient> and that is the problem, it used to be of type List<String> but that was before I started saving data in the database, now it must be List<Ingredient> which breaks the whole thing.
In the contoller I have the following (among other things, I think this is the only required code for the problem at hand but if you need more let me know):
#ModelAttribute
public void addIngredientsToModel(Model model) {
List<Ingredient> ingredients = new ArrayList<>();
ingredientRepo.findAll().forEach(i -> ingredients.add(i));
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type));
}
}
private List<Ingredient> filterByType(List<Ingredient> ingredients, Type type) {
return ingredients
.stream()
.filter(x -> x.getType()
.equals(type))
.collect(Collectors.toList());
}
And finally in my thymeleaf file I have:
<span class="text-danger"
th:if="${#fields.hasErrors('ingredients')}"
th:errors="*{ingredients}">
</span>
Which causes the error:
thymeleaf Failed to convert property value of type java.lang.String to required type java.util.List
Once again, when private List<Ingredient> ingredients; was private List<String> ingredients; it worked, but now it must be private List<Ingredient> ingredients; because of the way it is saved in the database but it breaks at this point, how to fix it?
I met the same problem, what we need here is a converter.
package tacos.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import tacos.Ingredient;
import tacos.data.IngredientRepository;
#Component
public class IngredientByIdConverter implements Converter<String, Ingredient> {
private IngredientRepository ingredientRepo;
#Autowired
public IngredientByIdConverter(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
#Override
public Ingredient convert(String id) {
return ingredientRepo.findOne(id);
}
}
An optimisation of Ian's answer above:
Fetch the ingredients in the constructor of the converter.
package com.joeseff.xlabs.chtp01_1.tacos.converter;
import com.joeseff.xlabs.chtp01_1.tacos.model.Ingredient;
import com.joeseff.xlabs.chtp01_1.tacos.respository.jdbc.IngredientRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* #author - JoeSeff
* #created - 23/09/2020 13:41
*/
#Component
public class IngredientConverter implements Converter<String, Ingredient> {
private final IngredientRepository ingredientRepo;
private final List<Ingredient> ingredients = new ArrayList<>();
#Autowired
public IngredientConverter(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
ingredientRepo.findAll().forEach(ingredients::add);
}
#Override
public Ingredient convert(String ingredientId) {
return ingredients
.stream().filter( i -> i.getId().equals(ingredientId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Ingredient with ID '" + ingredientId + "' not found!"));
}
}
I return an object instance of the following class from a Spring RestController method.
package x
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
#XmlRootElement(name = "invoices")
public class Invoices implements Serializable {
private Info info;
private Set<Customer> customers = new HashSet<>();
private List<Invoice> invoices = new ArrayList<>();
public Info getInfo() {
return info;
}
public void setInfo(Info info) {
this.info = info;
}
#XmlElement(name = "customer")
public Set<Customer> getCustomers() {
return customers;
}
public void setCustomers(Set<Customer> customers) {
this.customers = customers;
}
#XmlElement(name = "invoice")
public List<Invoice> getInvoices() {
return invoices;
}
public void setInvoices(List<Invoice> invoices) {
this.invoices = invoices;
}
}
The Controller method has the signature;
#RequestMapping(value = "/invoice", method = RequestMethod.GET, produces = "application/xml; charset=UTF-8")
This returns an XML with an unexpected div element and an attribute named slick_uniqueid on the top element. How do I get rid of this, and where does this come from?
<invoices slick-uniqueid="3">
<div>
<a id="slick_uniqueid" />
</div>
I found the answer to this myself. The raw response from the server does not include this attribute, nor the extra element. It's chrome that modifies the XML slightly when it displays it in-browser. The attribute and element is not there if I do a 'view source' either. Strange. I have never noticed that before