Unexpected and unwanted div element in return from Spring RestController - spring

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

Related

JPA - Converter class is not being invoked as expected

I cannot figure out why my converter class is not being called. I have the following Entity class:
import org.springframework.validation.annotation.Validated;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.List;
#Entity
#Validated
#Table(name = "c_mark", schema="common")
public class CMark {
#Id
#Column(name = "c_mark_id")
private String id;
#Column(name = "cl_fk")
private String cFk;
#Column(name = "s_con")
#Convert(converter = StringListConverterCommaDelim.class)
private List<String> sCon;
#Override
public String toString() {
return "ClassificationMarking{" +
"id='" + id + '\'' +
", cFk='" + cFk + '\'' +
", s_con='" + s_con + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCFk() {
return cFk;
}
public void setCFk(String cFk) {
this.cFk = cFk;
}
public List<String> getSCon() {
return sCon;
}
public void setSCon(List<String> sCon) {
this.sCon = sCon;
}
}
Here is the converter class:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.Arrays;
import java.util.List;
import static java.util.Collections.emptyList;
#Converter
public class StringListConverterCommaDelim implements AttributeConverter<List<String>, String>
{
private static final String SPLIT_CHAR = ",";
#Override
public String convertToDatabaseColumn(List<String> stringList) {
return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";
}
#Override
public List<String> convertToEntityAttribute(String string) {
return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList();
}
}
Here is the repo interface that defines the insert method:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface CMarkRepository extends JpaRepository <ClassificationMarking, String> {
#Transactional
#Modifying
#Query(value = "INSERT INTO c_mark(c_fk, s_con) " +
"values(:c_fk, :s_con)", nativeQuery = true)
int insertCMark(#Param("c_fk") String c_fk, #Param("s_con") String s_con);
}
, and finally the method that invokes the insert:
public int postCMark(CMark cMark) {
CMark cm = null;
int status = 0;
try {
status = cMarkingRepository.insertCMark(cMark.getCFk(), cMark.getSCon());
} catch ( Exception e) {
e.printStackTrace();
}
return status;
}
My expectation is that the conversion takes place from the insertCMark() call? I am not sure. In any event, the converter is never called. I would be grateful for any ideas. Thanks!
You're not inserting the whole Entity. So I guess from Spring perspective you just give normal String parameters and Spring probably doesn't know the parameter should somehow be converted.
Despite that shouldn't you even get a compile error because you try to call insertCMark(String, String) as insertCMark(String, List<String>)?
Right now I would say that there is no need for some fancy Spring magic.
You can just tweak the getSCon() method to return a String and convert it in there. Or when you need it for something else to create a second method getSConString():
public String getSCon() {
return this.sCon != null ? String.join(SPLIT_CHAR, this.sCon) : "";
}
Another way is to use your current Converter by hand when calling insertCMark:
public int postCMark(CMark cMark) {
CMark cm = null;
int status = 0;
AttributeConverter<List<String>, String> converter = new StringListConverterCommaDelim();
String sCon = converter.convertToDatabaseColumn(cMark.getSCon());
try {
status = cMarkingRepository.insertCMark(cMark.getCFk(), sCon);
} catch ( Exception e) {
e.printStackTrace();
}
return status;
}

How Can we use Policy-enforcer dynamically in Java Springboot?

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.

Create a custom RestControllerAnotation to execute a requestMapping

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--

Get _links with spring RepositoryRestController

I have defined custom controllers for my repositories. For example, one looks like this
#RequestMapping(method = RequestMethod.GET, value = "/myEntities")
public ResponseEntity<?> get() {
...TO SOME STUFF
MyEntity myEntity= myEntityRepository.findById(1);
return ResponseEntity.ok(new Resource<>(myEntity));
}
This returns a JSON format data which includes a _links section, where I can get the href to the entity.
Now if I want to return an array of entities which are all resources, I get stuck.
What I have tried so far:
1.
#RequestMapping(method = RequestMethod.GET, value = "/myEntities")
public ResponseEntity<?> get() {
...TO SOME STUFF
List<MyEntity> myEntityList= myEntityRepository.findAll(1);
return ResponseEntity.ok(new Resources<>(myEntityList));
}
2.
#RequestMapping(method = RequestMethod.GET, value = "/myEntities")
public ResponseEntity<?> get() {
...TO SOME STUFF
List<MyEntity> myEntityList= myEntityRepository.findAll();
List<Resource<MyEntity>> resources = new ArrayList<>();
myEntityList.forEach(me -> {
resources.add(new Resource<>(me));
})
return ResponseEntity.ok(resources);
}
Option 1. and 2. don't add _links to the result and I don't understand why. I have googled it a lot and you can add links manually but this seems to be a much clearer way. Can anybody understand, what I'm doing wrong?
There are answers at "adding association links to spring data rest custom exposed method" and "Enable HAL serialization in Spring Boot for custom controller method" for similar questions. I tried something a little different from those answers, for solving a similar situation, and it worked fine. I wish you get your problem solved.
It follows how I did solve my specific situation:
#PostMapping(value = "/myEntities/searchWithParams"
, consumes = MediaType.APPLICATION_JSON_VALUE
, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> searchWithParams(#RequestBody SearchParamsDtoClass params, PersistentEntityResourceAssembler assembler)
{
List<MyEntity> entities = myEntityService.searchWithParams(params);
List<PersistentEntityResource> resourcesList = new ArrayList<PersistentEntityResource>();
for (MyEntity entity: entities) {
PersistentEntityResource resource = assembler.toResource(entity);
resourcesList.add(resource);
}
Resources<PersistentEntityResource> resources = new Resources(resourcesList);
return ResponseEntity.ok(resources);
}
The Resources constructor accepts a collection of embedded content, which is not the same as a collection of links. You have to add the links manually.
Resources resources = new Resources();
resources.add(myEntityRepository
.findAll()
.stream()
.map(entry -> convertToLink(entry)
.collect(Collectors.toList()));
return ResponseEntity.ok(resources);
Here is an example that includes embedded content and pagination in a HAL compliant format.
import static java.util.Objects.*;
import static java.util.Optional.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.hateoas.MediaTypes.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import static org.springframework.http.HttpStatus.*;
import static org.springframework.http.MediaType.*;
import static org.springframework.web.bind.annotation.RequestMethod.*;
import java.util.ArrayList;
import javax.inject.Inject;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.hateoas.Link;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.codahale.metrics.annotation.Timed;
#RestController
#RequestMapping("/catalog/users")
public class UserResource {
private final #NotNull UserLocator locator;
private final #NotNull ElasticsearchOperations elasticsearchTemplate;
#Inject
public UserResource(
final #NotNull UserLocator locator,
final #NotNull ElasticsearchOperations elasticsearchTemplate
) {
this.locator = requireNonNull(locator, "locator cannot be null");
this.elasticsearchTemplate = requireNonNull(elasticsearchTemplate, "elasticsearchTemplate cannot be null");
}
/**
* GET /users : get all the users.
*
* #return the ResponseEntity with status 200 (OK) and the list of users in body
*/
#Timed
#RequestMapping(method = GET, produces = { HAL_JSON_VALUE, APPLICATION_JSON_VALUE })
public ResponseEntity<Representations<User>> allUsers(
#RequestParam(name = "page", required = false, defaultValue = "0") #Min(0) int page,
#RequestParam(name = "size", required = false, defaultValue = "25") #Min(1) int size,
#RequestParam(name = "like", required = false) String like
) {
final PageRequest pageRequest = new PageRequest(page, size, Sort.Direction.ASC, "surname.raw", "givenName.raw");
final Page<User> entries = elasticsearchTemplate.queryForPage(
new NativeSearchQueryBuilder()
.withQuery(startingWith(like))
.withPageable(pageRequest)
.build(),
User.class
);
final ArrayList<Link> links = new ArrayList<>();
links.add(linkTo(UserResource.class).withSelfRel());
if (!entries.isFirst()) {
links.add(linkTo(methodOn(UserResource.class).allUsers(0, size, like)).withRel("first"));
}
if (!entries.isLast()) {
links.add(linkTo(methodOn(UserResource.class).allUsers(entries.getTotalPages() - 1, size, like)).withRel("last"));
}
if (entries.hasNext()) {
links.add(linkTo(methodOn(UserResource.class).allUsers(entries.nextPageable().getPageNumber(), size, like)).withRel("next"));
}
if (entries.hasPrevious()) {
links.add(linkTo(methodOn(UserResource.class).allUsers(entries.previousPageable().getPageNumber(), size, like)).withRel("prev"));
}
final Representations<User> resourceList = new Representations<>(entries, Representations.extractMetadata(entries), links);
return ResponseEntity.ok(resourceList);
}
private QueryBuilder startingWith(String like) {
return isNull(like) ? null : matchPhrasePrefixQuery("_all", like);
}
/**
* GET /users/:identifier : get the "identifier" user.
*
* #param identifier the identifier of the role to retrieve
* #return the ResponseEntity with status 200 (OK) and with body the role, or with status 404 (Not Found) or with 410 (Gone)
*/
#Timed
#RequestMapping(value = "/{identifier}", method = GET, produces = APPLICATION_JSON_VALUE)
public ResponseEntity<UserRepresentation> aUser(
#PathVariable("identifier") #NotNull String identifier
) {
return ofNullable(this.locator.findOne(identifier))
.map(user -> toRepresentation(user))
.map(ResponseEntity::ok)
.orElse(notFound())
;
}
private #NotNull UserRepresentation toRepresentation(final #NotNull User role) {
final String id = role.getIdentifier();
final Link self = linkTo(methodOn(UserResource.class).aUser(id)).withSelfRel().expand(id);
final Link collection = linkTo(UserResource.class).withRel("collection");
return new UserRepresentation(role, self, collection);
}
protected final #NotNull <U> ResponseEntity<U> notFound() {
return new ResponseEntity<>(NOT_FOUND);
}
}
import java.util.Optional;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.core.Relation;
#Relation(value = "item", collectionRelation = "items")
public class UserRepresentation extends Representation<User> {
public UserRepresentation(User content, Iterable<Optional<Link>> links) {
super(content, links);
}
public UserRepresentation(User content, Link... links) {
super(content, links);
}
#SafeVarargs
public UserRepresentation(User content, Optional<Link>... links) {
super(content, links);
}
public UserRepresentation(User content) {
super(content);
}
}
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.Objects.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Representation<T> extends Resource<T> {
private final Map<String, ResourceSupport> embedded = new HashMap<>();
public Representation(final #NotNull T content) {
super(content, emptyList());
}
public Representation(final #NotNull T content, final #NotNull Link... links) {
super(content, links);
}
#SafeVarargs
public Representation(final #NotNull T content, final #NotNull Optional<Link>... links) {
this(content, (Iterable<Optional<Link>>) asList(links));
}
public Representation(final #NotNull T content, final #NotNull Iterable<Optional<Link>> links) {
super(content, emptyList());
asStream(links).forEach(this::add);
}
public void add(final #NotNull Optional<Link> link) {
if (null != link && link.isPresent()) super.add(link.get());
}
#JsonProperty("_embedded")
#JsonInclude(Include.NON_EMPTY)
public final #NotNull Map<String, ResourceSupport> getEmbedded() {
return this.embedded;
}
/**
* #param rel must not be {#literal null} or empty.
* #param resource the resource to embed
*/
public final void embed(final #NotNull #Size(min=1) String rel, final ResourceSupport resource) {
requireNonNull(rel, "rel cannot be null");
if (rel.trim().isEmpty()) {
throw new IllegalArgumentException("rel cannot be empty");
}
if (null != resource) {
this.embedded.put(rel, resource);
}
}
}
import javax.validation.constraints.NotNull;
import org.springframework.data.domain.Page;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.core.Relation;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
#Relation(collectionRelation = "items")
public class Representations<T> extends Resources<T> {
#JsonUnwrapped
#JsonInclude(JsonInclude.Include.NON_NULL)
private final PageMetadata metadata;
public Representations(Iterable<T> content) {
super(content);
this.metadata = null;
}
public Representations(Iterable<T> content, PageMetadata metadata) {
super(content);
this.metadata = metadata;
}
public Representations(Iterable<T> content, Iterable<Link> links) {
super(content, links);
this.metadata = null;
}
public Representations(Iterable<T> content, PageMetadata metadata, Iterable<Link> links) {
super(content, links);
this.metadata = metadata;
}
public Representations(Iterable<T> content, Link... links) {
super(content, links);
this.metadata = null;
}
public Representations(Iterable<T> content, PageMetadata metadata, Link... links) {
super(content, links);
this.metadata = metadata;
}
/**
* Returns the pagination metadata.
*
* #return the metadata
*/
#JsonProperty("page")
public PageMetadata getMetadata() {
return metadata;
}
public static <U> PageMetadata extractMetadata(final #NotNull Page<U> page) {
return new PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), page.getTotalPages());
}
}

form:select does not set the selected value

Basically I need a CRUD application for payments. Each payment is assigned to one account.
My jsp page shows the correct list of "account" objects but it does not set the selected account.
Question: How can I achieve a dropdown box with the assigned account pre-selected?
Question: The assignment (account to payment) works, but only with the below code in my PaymentDaoImpl.java (marked as workaround). Why is this the case?
PaymentDaoImpl.java
..
#Override
#Transactional
public int insertRow(Payment obj) {
// Session session = HibernateUtil.getSessionFactory().openSession();
Session session = sessionFactory.openSession();
// !!!! workaround?? If I don't do this, account won't be assigned
int accountId = obj.getAccount().getId();
Account account = (Account) session.get(Account.class, accountId);
obj.setAccount(account);
Transaction tx = session.beginTransaction();
session.saveOrUpdate(obj);
tx.commit();
Serializable id = session.getIdentifier(obj);
session.close();
return (Integer) id;
}
..
jsp:
<form:select path="account.id" >
<form:option value="-1" label="Select Account" />
<form:options items="${accountList}" itemValue="id" itemLabel="iban" />
</form:select>
Domain Account.java:
package com.beingjavaguys.domain;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.*;
import org.springframework.beans.factory.annotation.Autowired;
#Entity
public class Account {
#Id
#GeneratedValue
private int id;
private String iban;
private String bank;
private String beschreibung;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getIban() {
return iban;
}
public void setIban(String iban) {
this.iban = iban;
}
public String getBank() {
return bank;
}
public void setBank(String bank) {
this.bank = bank;
}
public String getBeschreibung() {
return beschreibung;
}
public void setBeschreibung(String beschreibung) {
this.beschreibung = beschreibung;
}
}
Domain Payment
package com.beingjavaguys.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import org.springframework.beans.factory.annotation.Autowired;
#Entity
public class Payment {
private int id;
private Account account;
private float amount;
private String text;
private String comment;
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne(cascade = CascadeType.ALL, targetEntity = Account.class, fetch=FetchType.EAGER)
#JoinColumn(name="fk_account")
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public float getAmount() {
return amount;
}
public void setAmount(float amount) {
this.amount = amount;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
PaymentController.java
package com.beingjavaguys.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.beingjavaguys.domain.Payment;
import com.beingjavaguys.services.AccountService;
import com.beingjavaguys.services.PaymentService;
#Controller
#RequestMapping("/payment")
public class PaymentController {
#Autowired
PaymentService dataService;
#Autowired
AccountService accountService;
#RequestMapping("form")
public ModelAndView getForm(#ModelAttribute Payment obj) {
ModelAndView mav = new ModelAndView("payment/form");
mav.addObject("accountList", accountService.getList());
return mav;
}
#RequestMapping("insert")
public ModelAndView insert(#ModelAttribute Payment obj) {
dataService.insertRow(obj);
return new ModelAndView("redirect:list");
}
#RequestMapping("list")
public ModelAndView getList() {
List objList = dataService.getList();
return new ModelAndView("payment/list","objList",objList);
}
#RequestMapping("delete")
public ModelAndView deleteUser(#RequestParam int id) {
dataService.deleteRow(id);
return new ModelAndView("redirect:list");
}
#RequestMapping("edit")
public ModelAndView editUser(#RequestParam int id,#ModelAttribute Payment obj) {
ModelAndView mav = new ModelAndView("payment/form");
Payment paymentObj = dataService.getRowById(id);
mav.addObject("accountList", accountService.getList());
mav.addObject("paymentObj", paymentObj);
return mav;
}
#RequestMapping("update")
public ModelAndView updateUser(#ModelAttribute Payment obj) {
dataService.updateRow(obj);
return new ModelAndView("redirect:list");
}
}
Can you have a look on my implementation of the AccountEditor? I need the AccountService to lookup the account, or not? However, I don't get the service instantiated here..
public class AccountEditor extends PropertyEditorSupport {
#Autowired
AccountService dataService; // == null ??
#Override
public void setAsText(String text) {
Account account = lookupAccount(text); // lookup account by accountId
// text
setValue(account);
}
private Account lookupAccount(String text) {
int id = Integer.parseInt(text);
return dataService.getRowById(id);
}
}
What you need is a PropertyEditor implementation to convert from a String accountId to an Account object. Then in your jsp you use the path form:select path="account" instead of form:select path="account.id"
Implement a PropertyEditor as follows
public class AccountEditor extends PropertyEditorSupport{
#Override
public void setAsText(String text){
Account account = lookupAccount(text); //lookup account by accountId text
setValue(account);
}
}
Then register your AccountEditor in your controller
#InitBinder
public void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Account.class , new AccountEditor());
}
With the above changes you wont need your workaround. Your solution is only setting the id property of the account and not the whole object

Resources