How does one override this `/error` endpoint? - spring

I am studying Spring OAuth by decomposing this set of three interconnected apps at GitHub, while also carefully studying the Spring OAuth 2 Developer Guide at this link. The Developer Guide says that the /oauth/error endpoint needs to be customized, but what specific code should be use to accomplish a successful override of /oauth/error?
FIRST ATTEMPT:
My first attempt at doing the override, is throwing errors, which you can see in the debug log, which I have uploaded to a file sharing site at this link.
I started by trying to get code elements provided by Spring to work in the sample app.
First, I added a new controller class to the authserver app in the sample app link above, and I added some of the sample code from Spring's WhiteLabelErrorEndpoint.java, which you can read at this link. My attempt is as follows:
package demo;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.HtmlUtils;
#Controller
public class CustomViewsController {
private static final String ERROR = "<html><body><h1>OAuth Error</h1><p>${errorSummary}</p></body></html>";
#RequestMapping("/oauth/error")
public ModelAndView handleError(HttpServletRequest request) {
Map<String, Object> model = new HashMap<String, Object>();
Object error = request.getAttribute("error");
// The error summary may contain malicious user input,
// it needs to be escaped to prevent XSS
String errorSummary;
if (error instanceof OAuth2Exception) {
OAuth2Exception oauthError = (OAuth2Exception) error;
errorSummary = HtmlUtils.htmlEscape(oauthError.getSummary());
}
else {
errorSummary = "Unknown error";
}
model.put("errorSummary", errorSummary);
return new ModelAndView(new SpelView(ERROR), model);
}
}
And I added the following SpelView.java String handler class used by the link above by copying the code from this link:
package demo;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
/**
* Simple String template renderer.
*
*/
class SpelView implements View {
private final String template;
private final String prefix;
private final SpelExpressionParser parser = new SpelExpressionParser();
private final StandardEvaluationContext context = new StandardEvaluationContext();
private PlaceholderResolver resolver;
public SpelView(String template) {
this.template = template;
this.prefix = new RandomValueStringGenerator().generate() + "{";
this.context.addPropertyAccessor(new MapAccessor());
this.resolver = new PlaceholderResolver() {
public String resolvePlaceholder(String name) {
Expression expression = parser.parseExpression(name);
Object value = expression.getValue(context);
return value == null ? null : value.toString();
}
};
}
public String getContentType() {
return "text/html";
}
public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
throws Exception {
Map<String, Object> map = new HashMap<String, Object>(model);
String path = ServletUriComponentsBuilder.fromContextPath(request).build()
.getPath();
map.put("path", (Object) path==null ? "" : path);
context.setRootObject(map);
String maskedTemplate = template.replace("${", prefix);
PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper(prefix, "}");
String result = helper.replacePlaceholders(maskedTemplate, resolver);
result = result.replace(prefix, "${");
response.setContentType(getContentType());
response.getWriter().append(result);
}
}

To override the error view define a controller, e.g.
#Controller
public class ErrorController {
#RequestMapping("/oauth/error")
public String error(Map<String,Object> model) {
// .. do stuff to the model
return "error";
}
}
and then implement the "error" view. E.g. using Freemarker, in a Spring Boot app you create a file called "error.ftl" in the "templates" directory at the top of the classpath:
<html><body>Wah, there was an error!</body></html>
This is all just vanilla Spring MVC so please refer to the Spring user guide for more details.

Related

How to make advice in Spring & aspectJ

I tried to create Pointcut & advice for annotation Scheduled and public methods in com.example package, but it doesnt work. When I tryed to call services in com.example, advice doesnt work. Also for annotation #Scheduled it doesnt work. I try to read documentation, it seems that it should work, but in reality it doesnt work. Can please someone give me a point, how to solve this issue.
package com.dhl.common.logging;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import java.net.InetAddress;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
#Aspect
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
public class LoggingAspect {
private static final String DATE_FORMATTER= "MMM dd, yyyy'T'HH:mm:ss.SSS";
public static final String LOG_LEVEL_KEY = "LOG_LEVEL";
public static final String APP_NAME_KEY = "APP_NAME";
public static final String LOGGER_CLASS_NAME_KEY = "LOGGER_CLASS_NAME";
public static final String SERVER_NAME_KEY = "SERVER_NAME";
public static final String FORMATED_TIME_KEY = "FORMATED_TIME";
public static final String ENVIROMENT_KEY = "ENVIROMENT";
public static final String DISTRIBUTE_TRACE_ID_KEY = "DISTRIBUTE_TRACE_ID";
#Pointcut("#annotation(org.springframework.scheduling.annotation.Scheduled)")
private void scheduled() {}
#Pointcut("within(com.example..*)")
private void service() {}
#Around("scheduled() && service()")
public Object connectionAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
MDC.put(LOG_LEVEL_KEY, "INFO");
MDC.put(APP_NAME_KEY, "CRDB");
MDC.put(LOGGER_CLASS_NAME_KEY, joinPoint.getSourceLocation().getWithinType().toString());
String serverName = InetAddress.getLocalHost().getHostName();
MDC.put(SERVER_NAME_KEY, serverName);
MDC.put(FORMATED_TIME_KEY, getFormatedTime());
if(serverName != null) {
if(serverName.toUpperCase().contains("LOCALHOST")) {
MDC.put(ENVIROMENT_KEY,"LOCALHOST");
} else if(serverName.toUpperCase().contains("TEST")) {
MDC.put(ENVIROMENT_KEY,"TEST");
} else if(serverName.toUpperCase().contains("UAT")) {
MDC.put(ENVIROMENT_KEY,"UAT");
} else {
MDC.put(ENVIROMENT_KEY,"PRODUCTION");
}
}
MDC.put(DISTRIBUTE_TRACE_ID_KEY, UUID.randomUUID().toString());
try {
return joinPoint.proceed();
}
finally {
// Might as well clear all the MDC, not just the "myId"
MDC.clear();
}
}
private String getFormatedTime() {
LocalDateTime localDateTime = LocalDateTime.now(); //get current date time
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMATTER);
String formatDateTime = localDateTime.format(formatter);
return formatDateTime;
}
}
There are a list of things that you may forgot:
You may forgot to add the aspect class to your context, as I see that #Component is not on the code. There are a few ways to do it, i.e. i.e. through xml, programmatically or through spring's #ComponentScan.
While assigning point cut through annotation should work, we should take precaution to where the annotated method is called. Spring AOP will not trigger the Advice when the method is called from within the class itself. You can see why here.

How to pass request parameters as-is between REST service calls in a Spring Boot services application?

We are doing an architectural refactoring to convert a monolithic J2EE EJB application to Spring services. In order to do that I'm creating services by breaking the application against the joints of its domain. Currently, I have three of them and each calls another service via Rest.
In this project our ultimate purpose is transforming the application to microservices, but since cloud infrastructure isn't clear and probably won't be possible, we decided to make it this way and thought that since services using Rest, it will be easy to make the transform in future.
Does our approach makes sense? My question stems from this.
I send a request to UserService with a header parameter, userName from Postman.
GET http://localhost:8087/users/userId?userName=12345
UserService calls another service which calls another. Rest call order between services is this:
UserService ---REST--> CustomerService ---REST--> AlarmService
Since I'm doing the work of carrying the common request parameters like this right now, I need to set common header parameters in every method that making Rest requests by taking them from incoming request to outgoing request:
#RequestMapping(value="/users/userId", method = RequestMethod.GET)
public ResponseEntity<Long> getUserId(#RequestHeader("userName") String userName) {
...
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList
(MediaType.APPLICATION_JSON));
headers.set("userName", userName);
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
HttpEntity<Long> response =
restTemplate.exchange(CUSTOMER_REST_SERVICE_URI,
HttpMethod.GET, entity, Long.class);
...
}
UserService:
package com.xxx.userservice.impl;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
import java.util.Map;
#RestController
public class UserController extends AbstractService{
Logger logger = Logger.getLogger(UserController.class.getName());
#Autowired
private RestTemplate restTemplate;
private final String CUSTOMER_REST_SERVICE_HOST = "http://localhost:8085";
private final String CUSTOMER_REST_SERVICE_URI = CUSTOMER_REST_SERVICE_HOST + "/customers/userId";
#RequestMapping(value="/users/userId", method = RequestMethod.GET)
public ResponseEntity<Long> getUserId(#RequestHeader("userName") String userName) {
logger.info(""user service is calling customer service..."");
try {
//do the internal customer service logic
//call other service.
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList
(MediaType.APPLICATION_JSON));
headers.set("userName", userName);
HttpEntity<String> entity = new HttpEntity<>("parameters", headers);
HttpEntity<Long> response =
restTemplate.exchange(CUSTOMER_REST_SERVICE_URI,
HttpMethod.GET, entity, Long.class);
return ResponseEntity.ok(response.getBody());
} catch (Exception e) {
logger.error("user service could not call customer service: ", e);
throw new RuntimeException(e);
}
finally {
logger.info("customer service called...");
}
}
}
CustomerService:
package com.xxxx.customerservice.impl;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.xxx.interf.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class CustomerController extends AbstractService{
private final String ALARM_REST_SERVICE_HOST = "http://localhost:8086";
private final String ALARM_REST_SERVICE_URI = ALARM_REST_SERVICE_HOST + "/alarms/maxAlarmCount";
#Autowired
private CustomerService customerService;
#Autowired
private RestTemplate restTemplate;
...
#GetMapping(path="/customers/userId", produces = "application/json")
public long getUserId(#RequestHeader(value="Accept") String acceptType) throws RemoteException {
//customer service internal logic.
customerService.getUserId();
//customer service calling alarm service.
return restTemplate.getForObject(ALARM_REST_SERVICE_URI, Long.class);
}
}
AlarmService:
package com.xxx.alarmservice.impl;
import com.xxx.interf.AlarmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class PriceAlarmController extends AbstractService{
#Autowired
private AlarmService priceAlarmService;
#RequestMapping("/alarms/maxAlarmCount")
public long getMaxAlarmsPerUser() {
// alarm service internal logic.
return priceAlarmService.getMaxAlarmsPerUser();
}
}
I have tried these config and interceptor files but i can use them just for logging and can't transfer header parameters by using them. Probably because each service has them. And also, this interceptor only works in UserService which first uses RestTemplate to send request. Called service and first request which is coming from Postman doesn't work with it because they doesn't print any log message like UserService does.
CommonModule:
package com.xxx.common.config;
import com.xxx.common.util.HeaderRequestInterceptor;
import org.apache.cxf.common.util.CollectionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors
= restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new HeaderRequestInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
ClientHttpRequestInterceptor:
package com.xxx.common.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.nio.charset.Charset;
public class HeaderRequestInterceptor implements ClientHttpRequestInterceptor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException
{
log.info("HeaderRequestInterceptor....");
logRequest(request, body);
request.getHeaders().set("Accept", MediaType.APPLICATION_JSON_VALUE);
ClientHttpResponse response = execution.execute(request, body);
logResponse(response);
return response;
}
private void logRequest(HttpRequest request, byte[] body) throws IOException
{
log.info("==========request begin=======================");
}
private void logResponse(ClientHttpResponse response) throws IOException
{
log.info("==========response begin=============");
}
}
How can I manage the passing of common header information like userName by using some kind of interceptors or other mechanism in single place?
In your HeaderRequestInterceptor's intercept method, you can access the current http request and its headers (userId in your case) in the following way:
#Override
public ClientHttpResponse intercept(HttpRequest request..
...
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String userId = httpServletRequest.getHeader("userId");
request.getHeaders().set("userId", userId);

How do I read the post data in a Spring Boot Controller?

I would like to read POST data from a Spring Boot controller.
I have tried all the solutions given here: HttpServletRequest get JSON POST data, but I still am unable to read post data in a Spring Boot servlet.
My code is here:
package com.testmockmvc.testrequest.controller;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
#Controller
public class TestRequestController {
#RequestMapping(path = "/testrequest")
#ResponseBody
public String testGetRequest(HttpServletRequest request) throws IOException {
final byte[] requestContent;
requestContent = IOUtils.toByteArray(request.getReader());
return new String(requestContent, StandardCharsets.UTF_8);
}
}
I have tried using the Collectors as an alternative, and that does not work either. What am I doing wrong?
First, you need to define the RequestMethod as POST.
Second, you can define a #RequestBody annotation in the String parameter
#Controller
public class TestRequestController {
#RequestMapping(path = "/testrequest", method = RequestMethod.POST)
public String testGetRequest(#RequestBody String request) throws IOException {
final byte[] requestContent;
requestContent = IOUtils.toByteArray(request.getReader());
return new String(requestContent, StandardCharsets.UTF_8);
}
}

How to use spring to marshal and unmarshal xml?

I have a spring boot project. I have a few xsds in my project. I have generated the classes using maven-jaxb2-plugin. I have used this tutorial to get a sample spring boot application running.
import org.kaushik.xsds.XOBJECT;
#SpringBootApplication
public class JaxbExample2Application {
public static void main(String[] args) {
//SpringApplication.run(JaxbExample2Application.class, args);
XOBJECT xObject = new XOBJECT('a',1,2);
try {
JAXBContext jc = JAXBContext.newInstance(User.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(xObject, System.out);
} catch (PropertyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
But my concern is that I need to have all the jaxb classes of the schema mapped. Also is there something in Spring that I can use to make my task easier. I have looked at the Spring OXM project but it had application context configured in xml. Does spring boot have anything that I can use out of the box. Any examples will be helpful.
Edit
I tried xerx593's answer and I ran a simple test using main method
JaxbHelper jaxbHelper = new JaxbHelper();
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(XOBJECT.class);
jaxbHelper.setMarshaller(marshaller);
XOBJECT xOBJECT= (PurchaseOrder)jaxbHelper.load(new StreamSource(new FileInputStream("src/main/resources/PurchaseOrder.xml")));
System.out.println(xOBJECT.getShipTo().getName());
It ran perfectly fine. Now I just need to plug it in using spring boot.
OXM is definitely the right for you!
A simple java configuration of a Jaxb2Marshaller would look like:
//...
import java.util.HashMap;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
//...
#Configuration
public class MyConfigClass {
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(new Class[]{
//all the classes the context needs to know about
org.kaushik.xsds.All.class,
org.kaushik.xsds.Of.class,
org.kaushik.xsds.Your.class,
org.kaushik.xsds.Classes.class
});
// "alternative/additiona - ly":
// marshaller.setContextPath(<jaxb.context-file>)
// marshaller.setPackagesToScan({"com.foo", "com.baz", "com.bar"});
marshaller.setMarshallerProperties(new HashMap<String, Object>() {{
put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
// set more properties here...
}});
return marshaller;
}
}
In your Application/Service class you could approach like this:
import java.io.InputStream;
import java.io.StringWriter;
import javax.xml.bind.JAXBException;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
#Component
public class MyMarshallerWrapper {
// you would rather:
#Autowired
private Jaxb2Marshaller marshaller;
// than:
// JAXBContext jc = JAXBContext.newInstance(User.class);
// Marshaller marshaller = jc.createMarshaller();
// marshalls one object (of your bound classes) into a String.
public <T> String marshallXml(final T obj) throws JAXBException {
StringWriter sw = new StringWriter();
Result result = new StreamResult(sw);
marshaller.marshal(obj, result);
return sw.toString();
}
// (tries to) unmarshall(s) an InputStream to the desired object.
#SuppressWarnings("unchecked")
public <T> T unmarshallXml(final InputStream xml) throws JAXBException {
return (T) marshaller.unmarshal(new StreamSource(xml));
}
}
See Jaxb2Marshaller-javadoc, and a related Answer
If you just want serializing/deserializing bean with XML. I think jackson fasterxml is one good choice:
ObjectMapper xmlMapper = new XmlMapper();
String xml = xmlMapper.writeValueAsString(new Simple()); // serializing
Simple value = xmlMapper.readValue("<Simple><x>1</x><y>2</y></Simple>",
Simple.class); // deserializing
maven:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Refer: https://github.com/FasterXML/jackson-dataformat-xml
Spring BOOT is very smart and it can understand what you need with a little help.
To make XML marshalling/unmarshalling work you simply need to add annotations #XmlRootElement to class and #XmlElement to fields without getter and target class will be serialized/deserialized automatically.
Here is the DTO example
package com.exmaple;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.Date;
import java.util.Random;
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Setter
#XmlRootElement
public class Contact implements Serializable {
#XmlElement
private Long id;
#XmlElement
private int version;
#Getter private String firstName;
#XmlElement
private String lastName;
#XmlElement
private Date birthDate;
public static Contact randomContact() {
Random random = new Random();
return new Contact(random.nextLong(), random.nextInt(), "name-" + random.nextLong(), "surname-" + random.nextLong(), new Date());
}
}
And the Controller:
package com.exmaple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
#RequestMapping(value="/contact")
public class ContactController {
final Logger logger = LoggerFactory.getLogger(ContactController.class);
#RequestMapping("/random")
#ResponseBody
public Contact randomContact() {
return Contact.randomContact();
}
#RequestMapping(value = "/edit", method = RequestMethod.POST)
#ResponseBody
public Contact editContact(#RequestBody Contact contact) {
logger.info("Received contact: {}", contact);
contact.setFirstName(contact.getFirstName() + "-EDITED");
return contact;
}
}
You can check-out full code example here: https://github.com/sergpank/spring-boot-xml
Any questions are welcome.
You can use StringSource / StringResult to read / read xml source with spring
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
#Override
public Service parseXmlRequest(#NonNull String xmlRequest) {
return (Service) jaxb2Marshaller.unmarshal(new StringSource(xmlRequest));
}
#Override
public String prepareXmlResponse(#NonNull Service xmlResponse) {
StringResult stringResult = new StringResult();
jaxb2Marshaller.marshal(xmlResponse, stringResult);
return stringResult.toString();
}

Spring - Failed to convert property value of type java.lang.String to required type

I am making a project of the Housing Association in Spring.
When I'm trying to add an object to my list of apartments I'm getting an error that is written somehow on the page:
https://s28.postimg.org/vrhy6mbd9/blad.jpg
Apartments have relation Many to One Building.
Apartment Controller:
package pl.dmcs.spoldzielnia.controllers;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.ModelAndView;
import pl.dmcs.spoldzielnia.domain.Apartment;
import pl.dmcs.spoldzielnia.service.ApartmentService;
import pl.dmcs.spoldzielnia.service.BuildingService;
#Controller
#SessionAttributes
public class ApartmentController {
#Autowired
ApartmentService apartmentService;
#Autowired
BuildingService buildingService;
#RequestMapping("admin/apartment")
public String listApartment(Map<String, Object> map, HttpServletRequest request) {
int apartmentId = ServletRequestUtils.getIntParameter(request, "apartmentId" , -1);
if (apartmentId > 0)
{
Apartment apartment = apartmentService.getApartment(apartmentId);
apartment.setBuilding(buildingService.getBuilding(apartmentService.getApartment(apartmentId).getBuilding().getId()));
map.put("selectedBuilding", apartmentService.getApartment(apartmentId).getBuilding().getId());
map.put("apartment", apartment);
}
else
map.put("apartment", new Apartment());
map.put("buildingList", buildingService.listBuilding());
map.put("apartmentList", apartmentService.listApartment());
return "apartment";
}
#RequestMapping(value = "admin/addApartment", method = RequestMethod.POST)
public String addContact(#ModelAttribute("apartment") Apartment apartment, BindingResult result,
HttpServletRequest request, Map<String, Object> map) {
if (result.getErrorCount()==0)
{
if (apartment.getId()==0)
{
if (apartment.getBuilding().getId() > 0)
apartment.setBuilding(buildingService.getBuilding(apartment.getBuilding().getId()));
apartmentService.addApartment(apartment);
}
else
{
apartmentService.editApartment(apartment);
}
return "redirect:/admin/apartment.html";
}
map.put("buildingList", buildingService.listBuilding());
map.put("apartmentList", apartmentService.listApartment());
return "apartment";
}
#RequestMapping("admin/delete/apartment/{apartmentId}")
public String deleteApartment(#PathVariable("apartmentId") Integer apartmentId) {
apartmentService.removeApartment(apartmentId);
return "redirect:/admin/apartment.html";
}
// #RequestMapping("/apartment")
// public ModelAndView showContacts() {
//
// return new ModelAndView("apartment", "command", new Apartment());
// }
Domain:
package pl.dmcs.spoldzielnia.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name="apartment")
public class Apartment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
int id;
#Column(name="apartmentNumber", nullable=false)
private String number;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne
private Building building;
public Building getBuilding() {
return building;
}
public void setBuilding(Building building) {
this.building = building;
}
}
}
Building Service Implementation:
package pl.dmcs.spoldzielnia.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pl.dmcs.spoldzielnia.dao.BuildingDAO;
import pl.dmcs.spoldzielnia.domain.Building;
import pl.dmcs.spoldzielnia.domain.Building;
#Service
#Transactional
public class BuildingServiceImpl implements BuildingService{
#Autowired
BuildingDAO buildingDAO;
#Transactional
public void addBuilding(Building building) {
buildingDAO.addBuilding(building);
}
#Transactional
public List<Building> listBuilding() {
return buildingDAO.listBuilding();
}
#Transactional
public Building getBuilding(int id) {
return buildingDAO.getBuilding(id);
}
#Transactional
public void removeBuilding(int id) {
buildingDAO.removeBuilding(id);
}
#Transactional
public void editBuilding(Building building) {
buildingDAO.editBuilding(building);
}
}
Could you help me to solve my problem?
The problem is that you are assuming that Spring MVC is going to be able to fill your Apartment object from the data passed. From the form it looks like the Building number is 12, which probably is a unique identifier for the Building in the database, but how is Spring MVC going to know how to go to the database, retrieve the proper building object and put it into the Apartment object?
Remember that objects mapped through SpringMVC parameters are regular Java POJOs, not Hibernate attached entities. So, when the mapping occurs SpringMVC is trying to put "12" into the building attribute of type Building into your POJO (which explains the error you are getting).
You have two options:
First, you can register a custom formatter, that will use the passed id to retrieve a Building from the database:
import org.springframework.core.convert.converter.Converter;
public class BuildingIdToBuildingConverter implements Converter<String, Building> {
private BuildingService buildingService;
public BuildingIdToBuildingConverter(BuildingService buildingService) {
this.buildingService = buildingService;
}
#Override
public Building convert (String id) {
return buildingService.getBuilding(id);
}
}
And register it:
public class AppConfig extends WebMvcConfigurerAdapter {
...
#Bean
public BuildingService buildingService(){
return new BuildingService();
}
#Override
public void addFormatters (FormatterRegistry registry) {
registry.addConverter(new BuildingIdToBuildingConverter(buildingService()));
}
}
Second, do this work manually by sending the building id in a separate parameter:
#RequestMapping(value = "admin/addApartment", method = RequestMethod.POST)
public String addContact(#ModelAttribute("apartment") Apartment apartment, #RequestParam("buildingId") String buildingId, BindingResult result, HttpServletRequest request, Map<String, Object> map) {
if (result.getErrorCount()==0){
if (apartment.getId()==0){
apartment.setBuilding(buildingService.getBuilding(buildingId));
apartmentService.addApartment(apartment);
}
}
else{
apartmentService.editApartment(apartment);
}
return "redirect:/admin/apartment.html";
}
map.put("buildingList", buildingService.listBuilding());
map.put("apartmentList", apartmentService.listApartment());
return "apartment";
}
And change your HTML accordingly to send the buildingId value.

Resources