Java [Android] [Spring] -Deserialize a byte array - java

So basically im working on an android app and i have a back-end server. I send the server a byte array, and it stores it in the database. I'm using Spring, so the Entity where the byte array is stored is this:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String name;
private String lastName;
private int age;
#Lob
private byte[] imageBytes;
}
When the server receives the byte array, it receives and stores something like this : [-1, -40, -1, .....]
Now when sending it back, i put it inside a UserDto object as so:
public class UserDto {
String username;
String password;
String name;
String lastName;
int age;
String location;
Long id;
byte [] imageBytes;
}
UserDto userDto = new UserDto();
userDto.setImageBytes(applicationUser.getImageBytes());
return userDto;
On my android app, im expecting it the call to return a ImageBytesResponse, as so:
public class ImageBytesResponse {
byte[] imageBytes;
public byte[] getImageBytes() {
return imageBytes;
}
public void setImageBytes(byte[] imageBytes) {
this.imageBytes = imageBytes;
}
}
The call method : Call<ImageBytesResponse> getImageBytes
However when making the call, when it returns to the android app, i get a
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING at line 1 column 110 path $.imageBytes
So basicily, why does going from UserDto --> ImageByteResponse not work?
Why is it not deserilizing properly? Is the fact that my entity attribute is a #Lob a problem ?

Related

Update User Detail api returns DataIntegrityViolationException

I'm creating an update API that updates the profile of the super admin, I mapped the member table to a DTO, on the member table password is set to not null and I did not include the password field on the dto because there's a provision for that be, when I tested the API on postman it returned on the console
DataIntegrityViolationException
SQL Error: 1048, SQLState: 23000
Column 'password' cannot be null
Here is my code
Dto
#Getter
#Setter
public class UpdateProfileDto {
#NotNull(message = "{member.firstName.notNull}")
#JsonProperty("first_name")
private String firstName;
#NotNull(message = "{member.lastName.notNull}")
#JsonProperty("last_name")
private String lastName;
#JsonProperty("nationality")
private Long nationality;
#JsonProperty("country_of_residence")
private Long countryOfResidence;
#JsonProperty("date_of_birth")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
#JsonFormat(pattern = "dd-MM-yyyy")
#Past(message = "{customer.dateOfBirth.past}")
private Date dateOfBirth;
#JsonProperty("current_job_title")
private String currentJobTitle;
#NotNull(message = "{member.emailAddress.notNull}")
#JsonProperty("email_address")
private String emailAddress;
#JsonProperty("username")
private String username;
#NotNull(message = "{member.phoneNumber.notNull}")
#PhoneNumber
#JsonProperty("phone_number")
private String phoneNumber;
#Size(max = 300, message = "{member.city.size}")
#JsonProperty("city")
private String city;
#Size(max = 300, message = "{member.state.size}")
#JsonProperty("state")
private String state;
}
ServiceImpl
#Override
#Transactional
public Member updateProfile(UpdateProfileDto body) {
Member superAdmin = repository.getOne(id);
if (superAdmin == null) {
throw new MemberNotFoundException(id);
}
Optional<Role> existingRole = roleJpaRepository.findByCode(RoleType.SUPER_ADMINISTRATOR.getValue());
if (existingRole.isEmpty()) {
throw new RoleNotFoundException(RoleType.SUPER_ADMINISTRATOR.getValue());
}
Member existing;
existing = mapper.map(body, Member.class);
existing.setPassword(superAdmin.getPassword());
existing.getRoles().add(existingRole.get());
existing.setNationality(countryRepository.getOne(body.getNationality()));
existing.setCountryOfResidence(countryRepository.getOne(body.getCountryOfResidence()));
return adminJpaRepository.save(existing);
}
Controller
#RestController
#RequestMapping(
value = "super-admin",
produces = { MediaType.APPLICATION_JSON_VALUE }
)
public class SuperAdminController {
private final SuperAdminService service;
public SuperAdminController(SuperAdminService service) {
this.service = service;
}
#PutMapping("/update")
public Member updateProfile(#Valid #RequestBody UpdateProfileDto body){
Member superAdmin = service.updateProfile(body);
return superAdmin;
}
}
The password bug has been fixed(changes reflected in serviceImpl), but when I run the code it returned Duplicate entry 'ijava#gmail.com-111803918380' for key 'member.email_address_phone_number_uq' email, and the phone number is set as a unique constraint in the member table, how can I bypass this?
You have few options, depending on your exact use case.
Extract existing password, using unique property in UpdateProfileDto, email looks like it can do the job.
Pseudocode:
Member existing = repository.findByEmail;
Member superAdmin = mapper.map(body, Member.class);
superAdmin.setPassword(existing.getPassword());
Set a dummy value for password, to be updated later on.
superAdmin.setPassword("dummy-password");
Make the column nullable in database.

Not Found when Returning Embedded Collection MongoDB Spring Boot

I am creating an application with embedded review documents inside Course documents with Spring Data Rest and MongoDB but I am unable to get reviews for a course. Here is my controller:
#Controller
#RequestMapping("/courses")
public class CourseController {
private final CourseRepository courseRepository;
public CourseController(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
#PatchMapping("/add-review")
public List<Review> addReview(#RequestBody AddReviewDto addReviewDto) {
Course course = courseRepository.findById(addReviewDto.getCourseId()).get();
Review review = new Review(new ObjectId().toString(), addReviewDto.getReview());
List<Review> reviews = course.getReviews();
reviews.add(review);
course.setReviews(reviews);
return courseRepository.save(course).getReviews();
}
#GetMapping("/{id}/reviews")
public List<Review> getAllReviewsForCourse(#PathVariable String id) {
Course course = courseRepository.findById(id).get();
return course.getReviews();
}
}
Here is the Course model:
#Getter
#Setter
#Document(collection = "courses")
#AllArgsConstructor
#NoArgsConstructor
public class Course {
public Course(#NotNull String code, #NotNull String name,
#NotNull String type, List<Review> reviews) {
this.code = code;
this.name = name;
this.type = type;
this.reviews = reviews;
}
#Id
private String id;
#NotNull
private String code;
#NotNull
private String name;
#NotNull
private String type;
private List<Review> reviews = new ArrayList<>();
}
And Review model:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Review {
#Id
private String id;
private String reviewText;
private String userName;
private String userId;
public Review(String id, Review other) {
this.id = id;
this.reviewText = other.reviewText;
this.userId = other.userId;
this.userName = other.userName;
}
}
When I send a request to http://localhost:8888/courses/605dc41f54beac4412cabadc, I successfully get reviews inside the course object as follows:
{
"code": "CS 101",
"name": "Introduction to Programming",
"type": "Lecture",
"reviews": [
{
"reviewText": "dfgsfgdgdg",
"userName": "yigit",
"userId": "604a9382777a83b08307c7e8"
}
]
}
But when I try to send the request to localhost:8888/courses/605dc41f54beac4412cabadc/reviews, I get 404 not found.
I debugged my code and seen that the code is running the correct controller, finding the course object and its reviews are visible in debugger but when I return course.getReviews(), it doesnt work.
You should be sending to http://localhost:8888/courses/605dc41f54beac4412cabadc/reviews
Try to open this URL in your browser
of course you get 404. Because your #RequestMapping is "/courses". try send the request to http://localhost:8888/courses/{id}/reviews
Turns out, I have used #Controller instead of #RestController...

Spring Boot server error while POST with Retrofit2, only happens when there are relationships in the database

I POST some data with Retrofit2 to a Spring Boot REST service and there are a lot of exceptions occurring in the server. This happens when I have relations in my database.
I have a REST service over a Spring Boot application that runs over the Heroku services, I was doing a login and a register tasks with an Android application, I am using Retrofit2 in Android to POST the data to the REST service, everything was working well until for some other reasons I create a relationship between users in my database, this relationship is a "follow", this is, create a relationship in a follow table where I have the ID of the user that is following and an ID of the user that is followed. When I create this relationship into the database and I try to login with the method that I created, I got a bunch of errors into the REST service that I do not know why is this happening.
So in Android I have the call of my Retrofit2 client and a method that creates the service passing as a parameter the UserService.class with the HTTP methods. I also pass as a parameter the user of the class User where have the information that I want to POST, then I call the enqueue method.
RetrofitClient.createService(UserService.class).login(user).enqueue(new CallBack<User>(){
#Override
public void onResponse(Call<User> call, Response<User> response) {
//Some logic here
}
});
Into my UserService.java I have the method that POST the user object information.
public interface UserService {
#POST("login")
public Call<User> login(#Body User user);
}
Now in the backend side I have a REST controller where I have the login endpoint that will be consumed for Retrofit2.
#PostMapping(path = "login", consumes = "application/json", produces = "application/json")
#CrossOrigin(origins = "*", methods= {RequestMethod.GET,RequestMethod.POST})
public Object login(#RequestBody String json) {
//Some logic here
}
As I said this endpoint runs fine when there are no relationships over a user into the DB, but when a user follow another one, this is, when there is a new row into the follow table, lets say:
follow table:
id_follow id_user_follower id_user_following
1 1 2
At the example above the user 1 follows the user 2, and when I try to login, this is, use the login method in the UserService class it throws me a bunch of errors.
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727)
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:145)
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:107)
com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:25)
And this repeat over 300 lines of errors.
The thing here is that between the bunch of error the server return a 200 HTTP response, I managed the exceptions in Spring Boot and I catch that, when I catch I send a code for an error to my Android Retrofit2 client, but the login does not work.
Expected result:
After sending the POST from Retrofit2 to Spring Boot the response have to be a HTTP 200 response but no exceptions have to happen into the server.
Actual result:
There is a 200 HTTP response from the server but there are a lot of exceptions into the server that return an error code to the Android application and the login does not work.
This is the entity that I want to return as JSON from the RestController in Spring Boot.
#Entity
#Table(name = "users")
public class User extends AuditModel{
private static final long serialVersionUID = 1700575815607801150L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idUser;
private String name;
private String lastName;
#Column(name = "nick_name", unique = true)
private String nickName;
private String avatarResource;
#Column(unique=true)
private String email;
private String password;
private String birthDate;
private String gender;
private String postalCode;
private int active;
public Long getIdUser() {
return idUser;
}
public void setIdUser(Long idUser) {
this.idUser = idUser;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getAvatarResource() {
return avatarResource;
}
public void setAvatarResource(String avatarResource) {
this.avatarResource = avatarResource;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthDate() {
return birthDate;
}
public void setBirthDate(String birthDate) {
this.birthDate = birthDate;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public Long getId() {
return idUser;
}
public void setId(Long idUser) {
this.idUser = idUser;
}
public int getActive() {
return active;
}
public void setActive(int active) {
this.active = active;
}
/* Relations */
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Comment> comments;
public List<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<UserMemory> userMemories;
public List<UserMemory> getUserMemories() {
return userMemories;
}
public void setUserMemories(List<UserMemory> userMemories) {
this.userMemories = userMemories;
}
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Vote> votes;
public List<Vote> getVotes() {
return votes;
}
public void setVotes(List<Vote> votes) {
this.votes = votes;
}
#OneToMany(mappedBy = "userFollower", fetch = FetchType.LAZY)
private List<Follow> usersFollowers;
public List<Follow> getUsersFollowers() {
return usersFollowers;
}
public void setUsersFollowers(List<Follow> usersFollowers) {
this.usersFollowers = usersFollowers;
}
#OneToMany(mappedBy = "userFollowing", fetch = FetchType.LAZY)
private List<Follow> usersFollowing;
public List<Follow> getUsersFollowing() {
return usersFollowing;
}
public void setUsersFollowing(List<Follow> usersFollowing) {
this.usersFollowing = usersFollowing;
}
}
By having fetchtype.LAZY, some of the values won't exist during serialization. This will make the ObjectMapper to try to fetch these and it all will end up in some kind of infinite loop.
It is never recommended to serialize #Entity annotated classes because database tables can change and that in turn will change the API for the calling clients.
Best way is to have specific ResponseDTOs that you transfer your data to before serialization so that the API and the database tables can change without breaking anything.
So, the problem was that in the backend I was using the writeValueAsString of the ObjectMapper class like this.
public Object register(#RequestBody String json){
User user = new User();
user.set...
...
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(user);
}
For some reason the writeValueAsString method gives a StackOverFlowException for a recursive behavior when there is a relationship into the database, this is a problem related with JPA or Hibernate, I am not sure about which one of those.
The solution was to write my own method to build a JSONObject with the information of my POJO:
public String buildUserJSON(User user) {
JSONObject userJson = new JSONObject();
userJson.put("idUser", user.getIdUser());
...
return userJson.toString();
}
And then call this method in my RestController to build the JSON that I want to return. As I said I do not know what was the problem but at least this was the solution for me.
Note: I found the solution to this be cause in the past I was with a problem similar like this one but when I was trying to return a List<T> as a JSON, so, I though that was related with the same thing.
Regards.
The problem was with Jackson, so to get out of this problem you most use two annotations in the relations of your entities.
More information, please see the next link: Here is the answer
Hope it can help for anyone.

Sending Entity Object in Response which contains blob

I am trying to create a springboot usermanagement application.
I have an entity object which contains two blob elements.Here is my entity object.
#Entity
#Table(name="user_meta_profile")
public class UserMetaProfile implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "user_id")
private int user_id;
#Column(name = "resume_file")
#Lob
private Blob resume_file;
#Column(name = "photo")
#Lob
private Blob photo;
#Column(name = "username")
private String username;
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
public Blob getResume_file() {
return resume_file;
}
public void setResume_file(Blob resume_file) {
this.resume_file = resume_file;
}
public Blob getPhoto() {
return photo;
}
public void setPhoto(Blob photo) {
this.photo = photo;
}
public void setUsername(String username) {
this.username = username;
}
}
As you can see there are two blob items 'resume_file' and 'photo'.
I want to send back a JSON response to the API call.
My Controller code is as shown below.
#Controller
#RequestMapping("/v1")
public class UsersController {
#Autowired
private IUserMetaProfileService userMetaProfileService;
#GetMapping("MetaProfile/{id}")
public ResponseEntity<UserMetaProfile> getUserMetaProfileById(#PathVariable("id") Integer id) {
UserMetaProfile userMetaProfile = userMetaProfileService.getUsersById(id);
return new ResponseEntity<UserMetaProfile>(userMetaProfile, HttpStatus.OK);
}
}
But when I call the API, I get the exception:
"exception": "org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write JSON document: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain:
...
...nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
Since JSON cannot contain binary data you need to serialize those fields as something else. You have a couple of options:
If you intend to show the binary as an image (since yours is a photo) you can serialize it as a data uri.
Send links to photos instead and create a controller method that will output the binary data with the appropriate content type (beyond the scope here).
So for Option 1 you can do something like this:
#Entity
#Table(name="user_meta_profile")
public class UserMetaProfile implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "user_id")
private int user_id;
#Column(name = "resume_file")
#Lob
private Blob resume_file;
#Column(name = "photo")
#Lob
private Blob photo;
#Column(name = "username")
private String username;
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
#JsonIgnore // disable serializing this field by default
public Blob getResume_file() {
return resume_file;
}
// serialize as data uri insted
#JsonProperty("resumeData")
public String getResume() {
// just assuming it is a word document. you would need to cater for different media types
return "data:application/vnd.openxmlformats-officedocument.wordprocessingml.document;base64," + new String(Base64.getEncoder().encode(resume_file.getBytes()));
}
public void setResume_file(Blob resume_file) {
this.resume_file = resume_file;
}
#JsonIgnore // disable this one too
public Blob getPhoto() {
return photo;
}
// serialize as data uri instead
#JsonProperty("photoData")
public String getPhotoBase64() {
// just assuming it is a jpeg. you would need to cater for different media types
return "data:image/jpeg;base64," + new String(Base64.getEncoder().encode(photo.getBytes()));
}
public void setPhoto(Blob photo) {
this.photo = photo;
}
public void setUsername(String username) {
this.username = username;
}
}
For the photo bit the value of the photoData JSON attribute can be set directly as the src attribute of an img tag and the photo will be rendered in the HTML. With the resume file you can attach it as an href to a <a> tag with a download attribute so it can be downloaded:
<a href={photoData value here} download>Download Resume File</a>
Just as an FYI if the files are large the JSON will be huge and it might also slow down the browser.

List of Pojo not getting converted into Json using #ResponseBody

I have controller class as below:
#RequestMapping(value = "/Reporting/FilterAsJson", method = RequestMethod.POST)
public #ResponseBody PagedQueryResult<GetEntitlementOverviewReportResult> filterAsJson(#ModelAttribute GetEntitleReportQuery query, HttpSession session)
{
getEntitlementOverviewFromSession(session).updateFromQuery(query, session);
return queryDispatcher.dispatch(query);}
The POJO class GetEntitlementOverviewReportResult is :
public class GetEntitlementOverviewReportResult
{
private Long id;
private String customerName;
private Long customerId;
private String customerNumber;
private String createdOn;
private String itemCreationDate;
private String licenseStatus;
private String licenseType;
private String licenseStatusCode;
private String licenseID;
private Long requestId;
private String licenseRootID;
private String customerNameCS;
private String customerNumberCS;
// <with getters and setters for the variables>
}
The problem is when all the fields in bean class is being set, proper Json is getting returned as a response. But when only first 6 fields are getting set, the response fails with 500 error in the debugger tool and doesn't return back to the calling ajax method. I get an "internal error" pop up in the browser. What am i missing here? Is is not possible to leave out the other fields whose values are not being fetched? I also tried using #JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL) but it doesn't make any difference.

Categories