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

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?

Related

Is there any way to add a abstract method for JPA repository : findByEnumContaining(String enum) [enum is subString for possible ENUM values]

This is my JPA #Repository, here we can get list<Person> with findByFullNameContaining(String query) - by providing just substring of fullName in query
#Repository
public interface PersonRepository extends CrudRepository<Person,String> {
Optional<Person> findByFullName(String fullName);
List<Person> findByDepartment(Department department);
List<Person> findByFullNameContaining(String query);
}
Similarly, Can we perform something for ENUM values - by providing sub-string value of ENUM? How?
Eg.
public enum Department {
NORTH,
SOUTH,
EAST,
WEST
}
List<Person> findByDepartmentContaining(String query);
JPA #Entity Person:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
#Entity
#Table(name = "Person")
public class Person {
#Id
#NotNull(message = "Mobile number is required")
#Size(min = 10, max = 10, message = "Mobile no. must be 10 digits")
#Column(name = "person_id", unique = true, length = 10)
private String mobileNum;
#Transient
#NotNull(message = "Password is required")
#Size(min = 1, message = "Password cannot be empty")
private String password="****";
#NotNull(message = "Name cannot be empty")
#Size(min = 1, max = 255, message = "fullName must be 1-255 characters long")
#Column(name = "full_name")
private String fullName;
#Column(name = "department")
#Enumerated(EnumType.STRING)
#NotNull(message = "Department must be specified")
private Department department = Department.UNKNOWN;
public Person() {
}
public Person(String mobileNum, String fullName, String password, Department department) {
this.mobileNum = mobileNum;
this.password = password;
this.fullName = fullName;
this.department = department;
}
public String getMobileNum() {
return mobileNum;
}
public void setMobileNum(String mobileNum) {
this.mobileNum = mobileNum;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
#Override
public String toString() {
return "Person [fullName=" + fullName + ", mobileNum=" + mobileNum + ", password=" + password + ", department=" + department + "]";
}
}
The question does not specify what exactly the issue is when declaring such a method.
But having tried with spring boot 2.7 with PostgreSql Database the application throws the following runtime error:
java.lang.IllegalArgumentException: Parameter value [%some value%] did not match expected type [com.Department (n/a)]
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
The issue at least with hibernate is that it will expect a Department field in an entity to be always passed as parameter in it's own object type which is Department.
I don't think there is a way to avoid this, as this is an out of the box functionality for Hibernate.
I think however that the correct approach would not be to define this type of method but the following. Department already exists in application code so it is known at the time the query needs to be invoked. So I think that the following solution would be considered a better practice:
//Repository method to be used in PersonRepository
List<Person> findByDepartmentIn(List<Department> departments);
And then the repository could be invoked in the following way from the service layer.
//Service method to be used
public List<Person> findByDepartmentIn(String searchBy) {
List<Department> departments = Arrays.stream(Department.values()).filter(dep -> dep.getName().contains(searchBy)).collect(Collectors.toList());
return personRepository.findDepartmentIn(departments);
}

Spring Boot Java map Entity to DTO: array literal (strings) INSTEAD of array of objects

sample get request: http://localhost:3000/contact/1
What I got:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": [
{
"emailaddress": "email#gmail.com"
},
{
"emailaddress": "email#g.c"
}
]
}
What I want:
{
"id": 1,
"firstname": "First Name",
"lastname": "Last Name",
"emailaddresses": ["email#gmail.com","email#g.c"]
}
The code below:
PersonDto
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<EmailAddressDto> emailaddresses;
//getters setters
}
EmailAddressDto
public class EmailAddressDto {
private String emailaddress;
//getters and setters
}
the Service class
public PersonDto getPerson(Long personId) { //this is the method inside the class
Optional<PersonEntity> p = peopleRepository.findById(personId);
var dto = modelMapper.map(p.get(), PersonDto.class);
return dto;
}
I also have a PersonEntity class mapped one-to-many to an EmailAddressesEntity class.
I'm really new to spring/java - I couldn't figure out how to get the JSON structure I want.
You can just annotate emailaddress field of EmailAddressDto with #JsonValue and leave everything as is.
public class EmailAddressDto {
#JsonValue
private String emailaddress;
//getters and setters
}
Using the above the output of a sample:
PersonDto personDto = new PersonDto();
personDto.setId(1L);
personDto.setFirstname("John");
personDto.setLastname("Doe");
personDto.setEmailaddresses(Arrays.asList(new EmailAddressDto("john#doe.com"), new EmailAddressDto("foo#bar.com")));
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(personDto);
System.out.println(json);
is:
{"id":1,"firstname":"John","lastname":"Doe","emailaddresses":["john#doe.com","foo#bar.com"]}
I'd suggest that you use a List of Strings instead of a List of EmailAddressDto's.
Following reasons:
Since you only have one attribute in your Dto, you can easily just directly use a List of Strings instead.
You get the second JSON-Layout as a response to your GET-Request.
When using variant number 1 (with the List of EmailAddressDto), you will achieve a JSON-Response with multiple objects for your different E-Mail addresses.
Otherwise when you use variant number 2 (with the List of String), you will achieve a JSON-Response which looks like what you want to have.
So don't forget to change your entities aswell.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailAddresses;
//getters setters
}
If you can change your PersonDto that would be the easiest and cleanest way to do it.
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
private List<String> emailaddresses;
//getters setters
}
While mapping your entities you would need to map EmailAddressesEntity to a String representing it (emailaddress).
If this is not possible you will need a custom converter for EmailAddressDto as follows:
public class ListEmailAddressDtoConverter extends StdConverter<List<EmailAddressDto>, List<String>> {
#Override
public List<String> convert(List<EmailAddressDto> emailAddresses) {
return emailAddresses.stream().map(EmailAddressDto::getEmailaddress).collect(Collectors.toList());
}
}
Then you need to tell Jackson to use it:
public class PersonDto {
private Long id;
private String firstname;
private String lastname;
#JsonSerialize(converter = ListEmailAddressDtoConverter.class)
private List<EmailAddressDto> emailaddresses;
//getters setters
}

Spring Boot JPA returns List of List not List of Object

I am new to spring boot jpa. I would like to get return type as List but I am getting just List
My Entity Class
#Component
#Entity
#Table(name = "USERDB.USERS")
public class User() {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_SEQ")
#SequenceGenerator(sequenceName = "MY_SEQ_NAME", allocationSize = 1), name = "MY_SEQ")
#Column(name = "userId")
private long id;
#Column(name = "firstName")
private String fName;
#Column(name = "midName")
private String mName;
#Column(name = "lastName")
private String lName;
#Column(name = "email")
private String email;
#Column(name = "createdDate")
private Timestamp createdOn;
public User() {
this.createdOn = new Timestamp(System.currentTimeMillis()
}
//SETTERS & GETTERS
}
My Repository;
public interface UserRepository extends JpaRepository<User, String> {
#Query("SELECT id fName, lastName, email FROM User u WHERE u.fName=(:fName)")
public List<User> findByEmail(#Param("fName") String fName);
}
All I wanted is to get a json response as a User Array with key value pair like below
[
[
"id": 1001,
"fName": John",
"lName": "Doe",
"email": "johnd#example.com"
],
[
"id": 1002,
"fName": "John",
"lName": "Simmons",
"email": "johns#example.com"
],
]
but I am getting a list with just values as below.
[
[
1001,
"John",
"Doe",
"johnd#example.com"
],
[
1002,
"John",
"Simmons",
"johns#example.com"
],
]
I am not sure where I am doing wrong or is this how I am supposed to get ? This is a hypothetical example of my actual program. Please excuse me for any errors.
Here is my controller class
#Restcontroller
public class UserController {
#Autowired
UserRepository repo;
#GetMapping("/user/{fname}")
public List<User> getUserByName(
#PathVariable("fname") String fname) {
return repo.findByEmail(fname);
}
}
A few points on your code:
public interface UserRepository extends JpaRepository<User, String> {
Query("SELECT id fName, lastName, email FROM User u WHERE u.fName=(:fName)")
public List<User> findEmails(#Param("fName") String fName);
}
You are creating the repository with JpaRepository<User, String> but in your class User id is not of type String. Consider making the ID field a String.
The method is findEmails but it is returning a List<User>? I would expect such a function to return List<String> - each string of course being an email. You may consider renaming this function to avoid future confusion. Not a big deal though.
In your query:
#Query("SELECT id fName, lastName, email FROM User u WHERE u.fName=(:fName)")
You should change this to:
#Query("SELECT u.id u.fName, u.lastName, u.email FROM User u WHERE u.fName=(:fName)")
That should fix the serialization issue you are having.
I very much agree with the suggestion of Fermi-4. Using a better naming convention is key to have a better easy manageable code. You should follow them. To solve your problem, just do the following and it will solve your problem.
public interface UserRepository extends JpaRepository<User, long> {
public List<User> findByFname(String fName);
}
Also, consider changing USER Entity definition to add serializable implementation as below
#Component
#Entity
#Table(name = "USERDB.USERS")
public class User implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3L;
Create additional User constructor with only few arguments that you wish to fetch from table
public User(long id, String fName, String lastName, String email) {
this.id = id;
this.fName = fName;
this.lastName = lastName;
this.email = email;
}
Adjust JP query like following
#Query("SELECT new com.package.User(u.id, u.fName, u.lastName, u.email) FROM User u WHERE u.fName = :fName")
public List<User> findByEmail(#Param("fName") String fName);
replace com.package with your User's actual package

How can save order detail associated with the user and How can I return order data associated with the user details based the url parameters?

I have created the User and Order entities as bellow. What I want to achieve is that if http://localhost:8080/users/username? is given I want to return only the user detail based on username provided. if http://localhost:8080/users/username?detail=true, I want to return user detail and order details for the username provided. How can I achieve this?
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String userName;
private String password;
private String firstName;
private String lastName;
private String gender;
private String lastLoggedIn;
#OneToMany
List<Order> listOfOrder;
//constructors
//getter and setter
}
Order.java
#Entity
public class Order
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private float amount;
private String createdAt;
private String deliveredDate;
//constructors
//getter and setter
}
Controller.java
//CREATE CUSTOMER
#RequestMapping(method = POST, value = "/create")
public ResponseEntity createCustomerDetails(#RequestParam String userName, String password, String firstName,
String lastName, String gender) {
String lastLogged = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = new User(userName, password, firstName, lastName, gender, lastLogged);
userRepository.save(user);
return ResponseEntity.status(OK).body(user.getId() + " User were successfully saved");
}
//CREATE ORDER
#RequestMapping(method = POST, value = "/order/{userName}")
public ResponseEntity createOrder(#PathVariable ("userName") String userName, #RequestParam float amount)
{
String createdAt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
String deliveredDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = orderService.findUser(userName);
if (!user.equals(null))
{
Order order = new Order(amount,createdAt,deliveredDate);
user.getListOfOrder().add(order);
return ResponseEntity.status(OK).body("order details were saved under "+user.getUserName() + " "+user.getFirstName());
}
return ResponseEntity.status(NOT_FOUND).body(null + " was not found");
}
//GET THE USER DETAILS
#RequestMapping(method = GET, value = "/users/{userName}")
public ResponseEntity getUserDetail(#PathVariable("userName") String userName,
#RequestParam(defaultValue ="none", required = false) String detail) {
if (!detail.equals("none")){
return .....;
}else {
return ........;
}
}
UserRepository
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUserName(String userName);
}
If you're ok with doing the serialization manually, you can employ JsonView to determine what gets serialized.
https://www.baeldung.com/jackson-json-view-annotation
User.java
import com.fasterxml.jackson.annotation.JsonView;
public class User {
#JsonView(Views.Lite.class)
private String name;
#JsonView(Views.Full.class)
private List<Order> orders;
}
Views.java
public class Views {
public static class Lite {}
public static class Full extends Lite {}
}
UserController.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private ObjectMapper mapper;
#GetMapping("/user/{username}")
public ResponseEntity<String> getUserDetail(#PathVariable String username, #RequestParam(required = false) String detail) throws JsonProcessingException {
User user = userRepository.findByUserName(username);
Class viewClass = Views.Lite.class;
if (!StringUtils.isEmpty(detail)) {
viewClass = Views.Full.class;
}
return ResponseEntity.status(HttpStatus.OK)
.body(mapper.writerWithView(viewClass).writeValueAsString(user));
}
}

How to cache only when the json is valid

I have a spring rest api application that is using HATEOAS/PagingAndSortingRepository to do most of the heavy lifting.
I have implemented caching using guava but I am having issues where when the user cancels the request midway through an api call, it caches the incomplete json and re-serves it for 60 seconds.
I am trying to use the unless="" parameter of the #Cacheable annotation. Previously, I just used unless="#result == null" but that does not handle incomplete or invalid json.
This does not seem to work either. So now I am trying to use com.google.gson.JsonParser to parse the result and invalidate if applicable.
Repository
#RepositoryRestResource(path = "products", collectionResourceRel = "products")
public interface ProductEntityRepository extends PagingAndSortingRepository<ProductEntity, String> {
JsonParser parser = new JsonParser();
#Cacheable(value = CacheConfig.STORE_CACHE)
ProductEntity findByName(String name);
}
Cache Config
public final static String PRODUCTS_CACHE = "products";
#Bean
public Cache productsCache() {
return new GuavaCache(PRODUCTS_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(60, TimeUnit.SECONDS)
.build());
}
How do I detect invalid json in the unless="" parameter?
I figured out my own issue!
When I interrupted the api request to localhost/products and re-requested, I finally saw an error about not being able to fetch a onetomany mapping. I believe the error was lazy initialization error for a collection.
I solved this issue by adding #LazyCollection(LazyCollectionOption.FALSE) to my models where the #OneToMany and #ManyToOne mappings were decalared.
For example:
#Entity(name = "product")
#Table(name = "products", schema = "${DB_NAME}", catalog = "")
public class ProductEntity {
private Integer id;
private String name;
private List shipments = new ArrayList<>();
#Id
#Column(name = "id", nullable = false)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 10)
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "shipmentID", targetEntity=ShipmentEntity.class)
#LazyCollection(LazyCollectionOption.FALSE)
public Collection<ShipmentEntity> getShipments() { return shipments; }
public void setShipments(Collection<ShipmentEntity> shipments) { this.shipments = shipments; }
}

Resources