How to annotate request body to describe examples - spring

I'm struggling with describe the requestBody correctly.
I have this Dto as Request body:
public #Data class ContactDto {
#Parameter(description = "Mailadress required if messageType is MAIL")
private String mailAddress;
#Parameter(description = "Phonenumber required if messageType is not MAIL", example =
"0041791234567")
private String phoneNumber;
#Parameter(description = "Message type which will be used to inform the user", examples = {
#ExampleObject(name = "SMS", value = "SMS"),
#ExampleObject(name = "MAIL", value = "MAIL")
})
private MessageType messageType;
}
And this in the Controller:
#PostMapping(consumes = "application/json")
public ResponseEntity<Object> createWichtel(#RequestBody() final WichtelDetailsDto wichtelDetailsDto)
{
return new ResponseEntity<>(HttpStatus.CREATED);
}
I'm using Spring with springdoc-openapi-ui
But when I'm opening the swagger-ui, the description above does not show.
What is the error here?

Just use #ApiParam
public #Data class ContactDto {
#ApiParam(value = "Mailadress required if messageType is MAIL")
private String mailAddress;
#ApiParam(value = "Phonenumber required if messageType is not MAIL", example =
"0041791234567")
private String phoneNumber;
#ApiParam(value = "Message type which will be used to inform the user", example = "{(name = \"SMS\", value = \"SMS\")}")
private MessageType messageType;

Related

How to validate a field based on other field value in bean (pojo) class in Spring Boot using annotations

I have created a request class having some fields with getters & setters. Now I want to validate each & every field. So with this validation I need to check if the value for field1 is A then fields2 should be mandatory and if value for field1 is B then field3 should be mandatory and field2 will be optional. Consider the below pojo class.
public class CreateADTSpaceRequestDTO implements Serializable{
private static final long serialVersionUID = 5654993652896223164L;
#NotEmpty(message = "taskUId cannot be null/empty")
#JsonProperty(value = "taskUId")
private String taskUId;
#NotEmpty(message = "clientName cannot be null/empty")
#JsonProperty(value = "clientName")
private String clientName;
#NotEmpty(message = "SpaceType cannot be null/empty")
#JsonProperty(value = "spaceType")
private String spaceType;
public String getTaskUId() {
return taskUId;
}
public void setTaskUId(String taskUId) {
this.taskUId = taskUId;
}
public String getClientName() {
return clientName;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public String getSpaceType() {
return spaceType;
}
public void setSpaceType(String spaceType) {
this.spaceType = spaceType;
}
}
In the above class we have a field called clientName, so based on client name value I want to validate spaceType field.
For ex. if clientName = A then spaceType is mandatory and if clientName = B then spaceType is optional.
Please help me with your comments how we can have this kind of validation using annotations or using regex or any other way.

SpringBoot - 400 bad request after adding List of objects to input

I have a simple ReactJS/SpringBoot application which generates XML files used for software licenses.
This has been working fine, but now I'm trying to add an "attributes" table which has a many-to-one relationship with the license table. It will keep track of attributes specified in the front end that will be set to true on the license.
I've used these URLs as a guide for the backend (video and related code):
https://www.youtube.com/watch?v=8qhaDBCJh6I
https://github.com/Java-Techie-jt/spring-data-jpa-one2many-join-example
However, I'm getting a 400 error both on the update and the addition of a license when I try to use the updated code.
The front end seems to be working correctly.
Edit: looks like this is the culprit; although I haven't figured out why, yet.
Could not resolve parameter [0] in org.springframework.http.ResponseEntity<com.license.gen.app.model.License> com.license.gen.app.web.LicenseController.updateLicense(com.license.gen.app.model.License): JSON parse error: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS')
/endEdit
It's producing a JSON object with the attributes added as an array (e.g. at the end of the following object):
{
"id": 861,
"fullName": "johnsmith#abc.com",
"contact": "",
"requester": "johnsmith#abc.com",
"tag": "",
"company": "ACME",
"companyId": "ABC",
"template": "AN_4_2",
"product": "Analytics",
"expiration": "2022-04-15",
"macs": "11-11-11-11-11-11",
"typeId": "555",
"family": "FACILITY",
"systems": "2",
"licenseFilename": "license_johnsmith#abc.com.xml",
"url": "https://test-licenses.s3.amazonaws.com/license_johnsmith%40abc.com.xml",
"dateCreated": "2021-04-09T02:43:39.000+0000",
"dateUpdated": "2021-04-09T02:43:39.000+0000",
"user": {
"id": "00u560lmjop5poy624x6",
"name": "myname",
"email": "myname#gmail.com"
},
"attributes": [
"CONFIG_MSC_PARAMETERS",
"REPORTING"
]
}
Here is the updated License entity, with attributes added as a one-to-many List:
#EqualsAndHashCode
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
#Table(name = "licenses")
public class License {
#Id
#GeneratedValue
private Long id;
#NonNull
private String fullName;
private String contact;
private String requester;
private String tag;
private String company;
private String companyId;
private String template;
private String product;
private String expiration;
private String macs;
private String typeId;
private String family;
private String systems;
#ManyToOne(cascade = CascadeType.PERSIST)
private User user;
#OneToMany(targetEntity = Attribute.class,cascade = CascadeType.ALL)
#JoinColumn(name ="license_fk",referencedColumnName = "id")
private List<Attribute> attributes;
// getters, setters
...
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<Attribute> getAttributes() {
return attributes;
}
public void setAttributes(List<Attribute> attributes) {
this.attributes = attributes;
}
}
License Repository (no change):
package com.license.gen.app.model;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface LicenseRepository extends JpaRepository<License, Long>
{
License findByFullName(String fullName);
List<License> findAllByUserId(String id);
Page<License> findAll(Pageable pageable);
#Query("SELECT l FROM License l " +
"WHERE l.company LIKE %:company% " +
"OR l.macs LIKE %:macs% " +
"OR l.requester LIKE %:requester% " +
"OR l.tag LIKE %:tag% " +
"OR l.fullName LIKE %:fullName% " +
"OR l.template LIKE %:template% " +
"OR l.expiration LIKE %:expiration% " +
"OR l.family LIKE %:family% " +
"OR l.licenseFilename LIKE %:filename% " +
"OR l.product LIKE %:product%"
)
List<License> findBySearchString(
#Param("company") String company,
#Param("macs") String macs,
#Param("requester") String requester,
#Param("tag") String tag,
#Param("fullName") String fullName,
#Param("template") String template,
#Param("expiration") String expiration,
#Param("family") String family,
#Param("filename") String filename,
#Param("product") String product);
#Query("SELECT l FROM License l " +
"WHERE l.macs LIKE %:macs%"
)
List<License> findByMacs(
#Param("macs") String macs);
#Query("SELECT l FROM License l " +
"WHERE l.fullName LIKE %:fullName%"
)
List<License> findMatchesByFullName(
#Param("fullName") String fullName);
}
Attribute Entity (new):
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Attribute {
#Id
private Long id;
#NonNull
private String attribute;
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated = new Date();
#ManyToOne(cascade = CascadeType.PERSIST)
private License license;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
}
Attribute Repository (new):
public interface AttributeRepository extends JpaRepository<User, Long> {
}
And the License Controller:
#RestController
#RequestMapping("/api")
class LicenseController {
private final Logger log = LoggerFactory.getLogger(LicenseController.class);
private LicenseRepository licenseRepository;
private UserRepository userRepository;
private AttributeRepository attributeRepository;
public static String bucket;
public LicenseController(LicenseRepository licenseRepository,
UserRepository userRepository,
AttributeRepository attributeRepository) {
this.licenseRepository = licenseRepository;
this.userRepository = userRepository;
this.attributeRepository = attributeRepository;
}
.....
#PostMapping("/license")
ResponseEntity<LicensePojo> createLicense(#Valid #RequestBody License license,
#AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {
log.info("Request to create license: {}", license);
Map<String, Object> details = principal.getAttributes();
String userId = details.get("sub").toString();
// check to see if user already exists
Optional<User> user = userRepository.findById(userId);
license.setUser(user.orElse(new User(userId,
details.get("name").toString(), details.get("email").toString())));
if(license.getLicenseFilename() == null){
license.setLicenseFilename("");
}
License result = licenseRepository.save(license);
User myUser = license.getUser();
// Generate the license
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
result.setLicenseFilename(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
String origTypeId = String.valueOf(result.getTypeId());
String origId = String.valueOf(result.getId());
if ((origTypeId == null) || origTypeId.equalsIgnoreCase("")){
result.setTypeId(origId);
}
result = licenseRepository.save(result);
return ResponseEntity.created(new URI("/api/license/" + result.getId()))
.body(licensePojo);
}
#PutMapping("/license/{id}")
ResponseEntity<License> updateLicense(#Valid #RequestBody License license) {
List<Attribute> attributes = license.getAttributes();
License result = licenseRepository.save(license);
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
result.setLicenseFilename(fileName);
return ResponseEntity.ok().body(result);
}
...
}
As far as I can see, there are no error messages being generated. The IDE is showing the AttributeRepository isn't being used in the controller, but they may be because it's part of the underlying SpringData JPA code to implement it.
Any ideas what the problem might be?

Springboot #Size constraint on property of bean not triggered

I have the following API setup:
CONTROLLER:
#PostMapping(path = "/post", consumes = "application/json", produces = "application/json")
public ResponseEntity<Object> receiveData(#Valid #RequestBody ArrayList<Person> persondata) {
}
BEAN:
#Component
public class Person {
#JsonProperty(value="name",required = true)
#Size(min=1)
private String name;
public Person(#JsonProperty(value="name", required = true) #Size(min=1) String name) {
this.name = name;
}
When I send the following requestbody through postman, it does not trigger the #Size constraint as expected:
[
{"name":"Pamela"},
{"name":"John"},
{"name":""}
]

Enhanced Spring Data Rest delivers empty relations

in my current implementation using Spring-Boot, -HATEOAS, -Rest-Data I'm trying to spare some further rest calls and enhance my rest resource for credits to also deliver relations of a credit (see below account as ManyToOne and creditBookingClassPayments as OneToMany).
The problem now is that I'm not able to get it run. The call always delivers empty relations. I really would appreciate some help on this.
Here are the surroundings:
Credit.java
#Entity
#Getter
#Setter
public class Credit {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Setter(NONE)
#Column(name = "id")
private Long itemId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="account_id", nullable = false)
private Account account;
#OneToMany(mappedBy = "credit")
private List<CreditBookingClassPayment> creditBookingClassPayments = new ArrayList<>();
#NotNull(message="Please enter a valid short name.")
#Column(length = 10, nullable = false)
private String shortName;
#NotNull(message="Please enter a valid name.")
#Column(nullable = false)
private String name;
...
}
CreditRepositoryCustomImpl.java
uses QueryDsl to enhance the credit resource with its realation
...
#Override
public List<Credit> findDistinctByAccountItemIdNew(Long accountId) {
QCredit credit = QCredit.credit;
QAccount account = QAccount.account;
QCreditBookingClassPayment creditBookingClassPayment = QCreditBookingClassPayment.creditBookingClassPayment;
QBookingClass bookingClass = QBookingClass.bookingClass;
BooleanExpression hasAccountItemId = credit.account.itemId.eq(accountId);
List<Credit> credits = from(credit).where(hasAccountItemId)
.innerJoin(credit.account, account)
.leftJoin(credit.creditBookingClassPayments, creditBookingClassPayment)
.leftJoin(creditBookingClassPayment.bookingClass, bookingClass).groupBy(credit.itemId).fetch();
return credits;
}
...
CreditController.java
looking into responseBody here all (account and credit payments) is available for credits
#RepositoryRestController
public class CreditController {
#Autowired
private CreditRepository creditRepository;
#RequestMapping(value = "/credit/search/findAllByAccountItemIdNew", method= RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resources<PersistentEntityResource>> findAllByAccountItemIdNew(#RequestParam Long accountId, PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
List<Credit> credits = creditRepository.findDistinctByAccountItemIdNew(accountId);
Resources<PersistentEntityResource> responseBody = new Resources<PersistentEntityResource>(credits.stream()
.map(persistentEntityResourceAssembler::toResource)
.collect(Collectors.toList()));
return ResponseEntity.ok(responseBody);
}
}
CreditResourceIntegrTest.java
here creditResourcesEntity hold the credit but account is null and creditBookingClassPayment is an empty array
#Test
public void testFindAllByAccountItemId() throws URISyntaxException {
URIBuilder builder = new URIBuilder(creditFindAllByAccountItemIdRestUrl);
builder.addParameter("accountId", String.valueOf(EXPECTED_ACCOUNT_ID));
builder.addParameter("projection", "base");
RequestEntity<Void> request = RequestEntity.get(builder.build())
.accept(MediaTypes.HAL_JSON).acceptCharset(Charset.forName("UTF-8")).build();
ResponseEntity<Resources<Resource<Credit>>> creditResourcesEntity =
restTemplate.exchange(request, new ParameterizedTypeReference<Resources<Resource<Credit>>>() {});
assertEquals(HttpStatus.OK, creditResourcesEntity.getStatusCode());
//assertEquals(EXPECTED_CREDIT_COUNT, creditResourcesEntity.getBody().getContent().size());
}
Do I miss something?
Thanks for your help!
Karsten
Okay, PersistentEntityResourceAssembler doesn't support relations. But this could be handled by using projections.
CreditProjection.java
#Projection(name = "base" , types = Credit.class)
public interface CreditProjection {
String getShortName();
String getName();
List<CreditBookingClassPaymentProjection> getCreditBookingClassPayments();
BigDecimal getValue();
BigDecimal getInterestRate();
BigDecimal getMonthlyRate();
}
CreditBookingClassPaymentProjection.java
#Projection(name = "base" , types = CreditBookingClassPayment.class)
public interface CreditBookingClassPaymentProjection {
BookingClass getBookingClass();
CreditPaymentType getCreditPaymentType();
}
CreditController.java
#RepositoryRestController
public class CreditController {
#Autowired
private ProjectionFactory projectionFactory;
#Autowired
private CreditRepository creditRepository;
#RequestMapping(value = "/credit/search/findAllByAccountItemIdNew", method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#ResponseBody
public ResponseEntity<Resources<?>> findAllByAccountItemIdNew(#RequestParam Long accountId,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
List<Credit> credits = creditRepository.findDistinctByAccountItemIdNew(accountId);
List<PersistentEntityResource> creditResources = new ArrayList<>();
for (Credit credit : credits) {
// credit.getCreditBookingClassPayments()
PersistentEntityResource creditResource = persistentEntityResourceAssembler.toResource(credit);
creditResources.add(creditResource);
}
Resources<CreditProjection> responseBody = new Resources<CreditProjection>(credits.stream()
.map(credit -> projectionFactory.createProjection(CreditProjection.class, credit))
.collect(Collectors.toList()));
return ResponseEntity.ok(responseBody);
}
}

restTemplate returns "Count not extract response" exception

I have following code to consume a REST webservice and convert the results;however, when I run the code it returns following exception, I am also not sure how to handle other type of responses for example if a response with error code in its body is returned. I have found this questions 1 and 2 with similar topics but did not find much there.
Exception
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.project.web.FlightsResults] and content type [application/xml]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:110)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:576)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:537)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:244)
at com.project.web.ArticleController.showArticles(ArticleController.java:36)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
....
The rest template code is as following:
RestTemplate restTemplate = new RestTemplate();
Map<String, String> vars = new HashMap<String, String>();
vars.put("user", "username");
vars.put("key", "password");
vars.put("fl", "po");
AvailabilityResponse flightResults = restTemplate
.getForObject(
"http://example.com/availabilityRequest?user={user}&key={key}&fl_type={fl}",
AvailabilityResponse.class, vars);
System.err.println(">>"
+ flightResults.getFlightList().get(0).getFlightOptions()
.getFlightOption().size());
XMLElements
#XmlRootElement(name = "availabilityResponse")
#XmlAccessorType(XmlAccessType.FIELD)
public class AvailabilityResponse {
#XmlElement(name = "flightList")
private List<FlightList> flightList;
public AvailabilityResponse() {
this.flightList = new ArrayList();
}
public List<FlightList> getFlightList() {
return flightList;
}
public void setFlightList(List<FlightList> flightList) {
this.flightList = flightList;
}
}
#XmlRootElement(name = "flightList")
#XmlAccessorType(XmlAccessType.FIELD)
public class FlightList {
#XmlElement(name = "flightOptions")
private FlightOptions flightOptions;
public FlightOptions getFlightOptions() {
return flightOptions;
}
public void setFlightOptions(FlightOptions flightOptions) {
this.flightOptions = flightOptions;
}
}
#XmlRootElement(name = "flightOptions")
#XmlAccessorType(XmlAccessType.FIELD)
public class FlightOptions {
#XmlElement(name = "flightOption")
private List<FlightOption> flightOption;
public FlightOptions() {
this.flightOption = new ArrayList();
}
public List<FlightOption> getFlightOption() {
return flightOption;
}
public void setFlightOption(List<FlightOption> flightOption) {
this.flightOption = flightOption;
}
}
#XmlRootElement(name = "flightOption")
#XmlAccessorType(XmlAccessType.FIELD)
public class FlightOption {
#XmlElement(name = "viaIata")
private String viaIata;
#XmlElement(name = "fromDate")
private String fromDate;
#XmlElement(name = "toDate")
private String toDate;
#XmlElement(name = "fromTime")
private String fromTime;
#XmlElement(name = "toTime")
private String toTime;
#XmlElement(name = "flightNum")
private String flightNum;
#XmlElement(name = "class")
private String fclass;
#XmlElement(name = "flightlegs")
private List<FlightLeg> flightLegs;
#XmlElement(name = "prices")
private Prices prices;
public FlightOption() {
this.flightLegs = new ArrayList();
this.prices = new Prices();
}
getters and setters
#XmlRootElement (name = "prices")
#XmlAccessorType (XmlAccessType.FIELD)
public class Prices {
#XmlElement (name ="adult")
private float adult;
#XmlElement (name ="child")
private float child;
#XmlElement (name = "infant")
private float infant;
#XmlElement (name = "total")
private Total total;
getters and setters
#XmlRootElement (name = "total")
#XmlAccessorType (XmlAccessType.FIELD)
public class Total {
#XmlAttribute (name ="serviceCharge")
private float serviceCharge;
#XmlAttribute (name = "taxCharge")
private float taxCharge;
#XmlAttribute (name ="taxGeneral")
private float taxGeneral;
#XmlAttribute (name = "totalPrice")
private float totalPrice;
#XmlAttribute (name ="currency")
private String currency;
getters and setters
REST response
<availabilityResponse version="3">
<flightList fromIata="FRA" toIata="YYZ" flightsFound="8">
<flightOptions>
<flightOption>
<viaIata>FRA-YHZ-YYZ</viaIata>
<fromDate>2015-06-06</fromDate>
<fromTime>13:15:00</fromTime>
<toDate>2015-06-06</toDate>
<toTime>20:10:00</toTime>
<flightNum>DEA062</flightNum>
<class>C</class>
<flightlegs>
<flightlegdetail fromIata="FRA" toIata="YHZ">
<fromDate>2015-06-06</fromDate>
<fromTime>13:15:00</fromTime>
<toDate>2015-06-06</toDate>
<toTime>15:35:00</toTime>
<flightNum>DE6062</flightNum>
</flightlegdetail>
</flightlegs>
<flightlegs>
<flightlegdetail fromIata="YHZ" toIata="YYZ">
<fromDate>2015-06-06</fromDate>
<fromTime>18:50:00</fromTime>
<toDate>2015-06-06</toDate>
<toTime>20:10:00</toTime>
<flightNum>WS269</flightNum>
</flightlegdetail>
</flightlegs>
<prices currency="EUR" specialOffer="true">
<adult>724.22</adult>
<child>725.00</child>
<infant>73.00</infant>
<total serviceCharge="0.00" taxCharge="85.77" taxGeneral="85.77"
flightPrice="724.22" totalPrice="809.99" currency="EUR" />
</prices>
</flightOption>
<flightOption>
<viaIata>FRA-YYZ</viaIata>
.....
Your exception is because you have not registered message convertors for handling xml responses returned from the service. You can use xstream marshallers etc and there are plenty of example out there in web.
http://www.informit.com/guides/content.aspx?g=java&seqNum=546
https://spring.io/blog/2009/03/27/rest-in-spring-3-resttemplate

Resources