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.
Related
I am using mongodb along with a java spring platform as my storage system to store files and documents. As mongo has a limit of 15MB(Bson storage limit) to store files I have used GridFs extension to store my large files. I have implemented this part as follow:
DBObject metaData = new BasicDBObject();
metaData.put("uplad_dateTime", largeDocument.getUploadDateTime());
metaData.put("title", largeDocument.getName());
ObjectId id =gridFsTemplate.store(largeDocument.getData(), largeDocument.getName(), largeDocument.getContentType(), metaData);
largeDocument.setId(id.toString());
The problem is that Gridfs by default uses two fs.chunck and fs.files collections, But I need to store files information with unique file id in a custom document model described here:
#Setter
#Getter
#org.springframework.data.mongodb.core.mapping.Document(collection = "document")
public class Document {
#Id
private String id;
#NotNull
#Column(name = "document_size", nullable = false)
private long size;
#NotNull
#Column(name = "document_name", nullable = false)
private String name;
#NotNull
#Column(name = "content_type", nullable = false)
private String contentType;
#NotNull
#Column(name = "content_data", nullable = false)
private InputStream data;
#NotNull
#Column(name = "upload_date_time", nullable = false)
private LocalDateTime uploadDateTime;
#Column(name = "download_counter", nullable = false)
private long downloadCounter;
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private Document document;
private Builder() {
document = new Document();
document.setUploadDateTime(LocalDateTime.now());
}
public Builder id(String id) {
document.setId(id);
return this;
}
public Builder size(long size) {
document.setSize(size);
return this;
}
public Builder name(String name) {
document.setName(name);
return this;
}
public Builder contentType(String contentType) {
document.setContentType(contentType);
return this;
}
public Builder data(InputStream data) {
document.setData(data);
return this;
}
public Builder uploadDateTime(LocalDateTime uploadDateTime) {
document.setUploadDateTime(uploadDateTime);
return this;
}
public Builder downloadCounter(long downloadCounter) {
document.setDownloadCounter(downloadCounter);
return this;
}
public Document build() {
return document;
}
}
}
How can I change Gridfs to store file information in my model instead of fs.files? I appreciate any help.
First, gridfs provides a facility to associate arbitrary metadata with the uploaded files. This should serve most use cases.
If you require an application object associated with uploaded files, it must be stored in a separate collection.
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.
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 ?
I have a Java (uses JPA) project that looks like below.
On the DB side:
TableName - Userdetails
Stores ID, no of items ordered and status.
The status is taken from another table called status.
On the Java side:
UserRepository is the JPA which has 2 methods to store user data into
the DB, and to retrieve user data based on the status.
An enum called Status equivalent to the DB table status.
Question:
If I want to store the user data into the DB, how should I pass the status field in?
E.g. should I do userRepository.storeToUserDetails("1", "John", Status.CREATED)
If I want to retrieve the user data based on status, how do I pass the status field?
E.g. userRepository.findUserDetailsByStatusIn(Status.CREATED)
1.You may add into logic of project Service class and use him from main code. in its turn, class Service should use interface Reposytory
Add to Status enum values of code
public enum Status {
CREATED(200), ACTIVE(207), SUSPENDET(400), FINDED(500);
private long code;
public long getCode() {
return code;
}
StatusEnum(long code){
this.code = code;
}
}
public class UserService{
#Autovired
private UserRepository userRepository;
public storeToUserDetails(long id, String name, Status status){
User user = userRepository.getOne(id);
user.setName(name);
user.setStatus(status);
userRepository.update(user);
}
}
3. Create class StatusCode
#Entity(name = "status")
public class StatusCode {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "status")
private long status;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getStatusCode() {
return status;
}
public void setStatusCode(String statusCode) {
this.status = statusCode;
}
}
In entity User use setter and getter for Status:
#OneToOne(cascade = CascadeType.ALL)
private StatusCode statusCode;
public void setStatus(Status status){
StatusCode temp = new StatusCode();
temp.setStatusName(status.name());
temp.setId(status.getCode());
this.setStatus(temp);
}
public Status getStatus(){
if statusCode != null{
return Satus.valueOf(statusCode.getName())}
return null;
}
Repository:
public interface UserRepository extends JpaRepository<User, Long> {
}
methods getOne() and update() extends from JpaReposytory interfase
I am pretty new to java development but I have developed couple of production ready applications on PHP and Python. I am developing a REST api using spring boot framework and find it little confusion in terms to handling/parsing the request body. In the other languages I have worked on, it was much simpler.
If its in python/php, I need not define all the parameters of the request explicitly to handle the request body. But in java, I have to predefine all the request parameters in a POJO class and MAP it. So for every API endpoint I make, I will have to define all in a Java class as the data layer.
But in other languages, I dont need to map anything to an array, in php $_POST holds the data objects.
My question is
I have the following requests
1.
{
"category": "product/invoice/event",
"item_id": "Unique tool identifier id",
"platforms_id": "1",
"share_platform_settings": {
"fb_share_type": "page/profile",
"fb_share_name": "profilename/pagename",
"fb_id": "fb_uid/page_id"
}
}
2.
{
"category": "product/invoice/event",
"item_id": "Unique tool identifier id",
"platforms_id": "1",
"share_platform_settings": {
"twitter_username": "page/profile",
"twitter_user_access_token": "profilename/pagename"
}
}
I had written a class
import com.google.common.base.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
#Entity
public class User {
#Id
#NotNull
#Size(max = 64)
#Column(name = "id", nullable = false, updatable = false)
private String id;
private String category;
private String item_id;
private String platforms_id;
private String fb_share_type;
private String fb_share_name;
private String fb_id;
User() {
}
public User(final String id, final String category,final String item_id,final String platforms_id,final String fb_share_type,final String fb_share_name,final String fb_id) {
this.id = id;
this.category = category;
this.item_id = item_id;
this.platforms_id = platforms_id;
this.fb_share_type = fb_share_type;
this.fb_share_name = fb_share_name;
this.fb_id = fb_id;
}
public String getId() {
return id;
}
public String getCategory() {
return category;
}
public String getItemId() {
return item_id;
}
public String getFbShareType() {
return platforms_id;
}
public String getFbShareName() {
return category;
}
public String getFbId() {
return category;
}
public String setCategory(String category) {
return this.category = category;
}
public void setId(String id) {
this.id = id;
}
public void setItemId(String item_id) {
this.item_id = item_id;
}
public void setFbShareType(String fb_share_type) {
this.fb_share_type = fb_share_type;
}
public void setFbShareName(String fb_share_name) {
this.fb_share_name = fb_share_name;
}
public void setFbId(String fb_id) {
this.fb_id = fb_id;
}
#Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.add("item_id", item_id)
.add("fb_share_type", fb_share_type)
.add("fb_share_name", fb_share_name)
.add("fb_id", fb_id)
.add("category", category)
.toString();
}
}
I can map the request to the class using #RequestBody Request request,
but I have define the class Request with my request params. But my request params keeps changing, I have a different json request structure 2. What would I do in that case? What if if I have n number of different requests on the same API? Do I need to create classes for each of them? or define all the variables in this class itself? Or is there anyway, I dont need anyclass, is jackson dependency used for that?
Sorry if this a dump question, I am pretty new to java development and I really appreciate understanding a question like this :P
As you are using key/value parameters in your JSON, you will need to map it with a similar structure in the Backend so you will need to use a collection of type Map like Map<String, String> share_platform_settings in your Entity.
And your Entity will be like:
#Entity
public class User {
#Id
#NotNull
#Size(max = 64)
#Column(name = "id", nullable = false, updatable = false)
private String id;
private String category;
private String item_id;
private String platforms_id;
private Map<String, String> share_platform_settings;
//Constructors, getters and setters
}
This should work for you.