I'm trying to add a Map property to a User Domain
class User {
String id
String username
String password
....
Map<String, Follower> followers
//i've tried also without embeded and got the same error. also tried follower insead of followers
static embedded = ['followers']
}
class Follower {
String name
List<String> interests
}
I Have a restful controller the implements the save method
#Transactional
#Secured(['permitAll'])
def save(User user){
user.id = new ObjectId().encodeAsBase64()
user = user.insert(flush: true)
respond
}
Sadly i'm getting an exception:
java.lang.IllegalStateException: Cannot convert value of type [com.google.gson.JsonObject] to required type [Follower] for property 'followers[532dbe3b8fef86ebe3e64789]': no matching editors or conversion strategy found Around line 26 ofUserController.groovy
line 26 is : user = user.insert(flush: true)
example json request:
{
username : "roy",
password : "123456",
followers : {
"532dbe3b8fef86ebe3e64789" : {
name : "Roy",
interests : ["math"]
}
}
}
Any help will be greatly appreciated
Thanks!
Roy
you are trying to save JSONObject's as Follower instances. The straight forward way to solve the issue would be to convert those into Follower instances manually:
def save(User user){
user.id = new ObjectId().encodeAsBase64()
user.followers = user.followers.collect{ new Follower( it ) }
user = user.insert(flush: true)
respond
}
if you have more of such cases, you should register a property editor for conversion JSON <-> Follower
Related
creating a document with status as pending and generated-token then send mail
and immediately after sending mail retrieving document by generated-token value and changing the previous status pending to unverified.
Even though for updating the document status, first retrieved the existing document and then only updating it, still end up in creating two different documents for both the status.
#Document
public class VerificationInfo {
private LoginInfo user;
private String token;
private String verificationStatus = VerificationStatus.PENDING.getVerificationStatus();
}
daoService
public void updateStatus(VerificationInfo verificationToken, String status) {
VerificationInfo vt = verificationRepository.findByToken(verificationToken.getToken()).get(0);
vt.setVerificationStatus(status);
verificationRepository.save(vt);
}
repository
#Repository
public interface VerificationRepository extends MongoRepository<VerificationInfo, String> {
List<VerificationInfo> findByToken(String token);
List<VerificationInfo> findByUser(LoginInfo user);
}
db entries
{ "_id" : ObjectId("5f4e7486664e197f3d745b17"), "token" : "c82907b7-e13e-484d-89cf-92ea394b6f6d", "verificationStatus" : "pending", "_class" : "com.models.VerificationInfo" }
{ "_id" : ObjectId("5f4e748b664e197f3d745b18"), "token" : "c82907b7-e13e-484d-89cf-92ea394b6f6d", "verificationStatus" : "unverified", "_class" : "com.models.VerificationInfo" }
If the status is correct, the problem with you identification of the document (_id).
public class VerificationInfo {
#Id
ObjectId _id;
// Other fields
}
Here we set a unique id to each document. So when you create a object, it will create a new document. If the _id already exists in database, then it will update the document based on the particular id.
1. There is no _id present in the model class
You extends MongoRepository<VerificationInfo, String>, the second parameter is the type of id. But there is no any id found in your model class. (Usually we use ObjectId, but String also can be given)
2. It will always create new document when you get data from frontend
Since you don't have id, when you pass a data to updateStatus(VerificationInfo verificationToken, String status), it will create new id and set the data, that's why you always getting new document.
Suppose you are sending the data with existing id, then the existing document will be updated based on given id
Lets say I have a postgres table named Employee with the following columns:
ID
FirstName
LastName
Employment
Date
Manager
Department
I am interested in having a REST endpoint such that /employee/{ID} will return all information for that particular employee in JSON format, but if I specify /employee/{ID}/FirstName then it'd return particular employee's first name only in JSON format, /employee/{ID}/LastName would return the employee's last name in JSON format, and so on. Is there a good way to implement this instead of implementing an endpoint for accessing each column? Thanks.
A simple way to solve this, is to use a request param instead of querying for the URL. Using a param like fields you would have an URL like /employee/{id}?fields=FirstName,LastName. Using the code below you could have a Map<String, Object> that would be serialized to a JSON with your data. Like this:
#ResponseBody
public Map<String, Object> getPerson(#PathVariable("id") long id, #RequestParam("fields") String fields) throws Exception {
return personService.getPersonFields(id, fields);
}
#Service
class PersonService {
public Map<String, Object> getPersonFields(Long personId, String fields) throws Exception {
final Person person = personRepository.findById(personId);
if (person == null) {
throw new Exception("Person does not exists!");
}
String[] fieldsArray = fields.split(",");
Map<String, Field> personFields = Arrays.stream(person.getClass().getFields()).collect(Collectors.toMap(Field::getName, field -> field);
Map<String, Object> personFieldsReturn = new HashMap<>();
for (String field : fieldsArray) {
if (personFields.containsKey(field)) {
personFields.get(field).setAccessible(true);
personFieldsReturn.put(field, personFields.get(field).get(person));
personFields.get(field).setAccessible(false);
}
}
return personFieldsReturn;
}
}
This is not a good solution though. But it should work.
So as #dave mentioned in a comment you can have a REST endpoint /employee/{ID}/{column} and in your controller you will have a mapping between value of {column} argument and actual column name in database. If you do not want to redeploy your application when mapping changes you can put it in a separate properties file on a server outside of your jar/war and you can also add an additional endpoint to either reload mapping form file on a server or an endpoint that will allow to upload and parse a file with mapping directly to your application.
I would suggest you to use RepositoryRestResource from Spring Data.
First of all, create your entity:
public class Employee {
//props
}
Afterthat create Employee Repository:
#RepositoryRestResource(collectionResourceRel = "employee", path = "employee")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
List<Employee> findByLastName(#Param("firstName") String firstName);
}
And that's all, you will get:
discoverable REST API for your domain model using HAL as media type.
collection, item and association resources representing your mode.
paginating and sorting
and so on.
Check out the docs:
Spring Guide
Spring Dc
Spring Data Rest Project
I was reading this post :
JDBCTemplate set nested POJO with BeanPropertyRowMapper
This answer seems great : JDBCTemplate set nested POJO with BeanPropertyRowMapper
However another situation came to my mind and I was not able until now to find suitable answer :
What if I got a list of Users in my message Class instead :
class User {
Long id;
String name;
}
class Message {
Long id;
String name;
List<User> users;
}
Could it be even possible with sql request manually (no jpa or crud repository) and row mapper to map every User for One Message and to make something like ? :
public class MessageMapper implements RowMapper<Message> {
public Message mapRow(ResultSet rs, int i) throws SQLException{
Message message= new Message();
message.setId(rs.getLong("messageId");
message.setName(rs.getString("messageName"));
User user = new User();
// or List<User> users = new ArrayList<>(); ??? ...
user.setUsers(users);
return message ;
}}
Thank you !
I have the following Entity class:
#Entity
#Table(name="reporteddevicedata", schema="schemaName")
public class MobileDeviceData {
#EmbeddedId
MobileDeviceDataId mobileDeviceDataId;
#Column(name="activitydetecteddate")
private ZonedDateTime activityDetectedDate;
public void setFlagId(int flagId) {
mobileDeviceDataId.setFlagId(flagId);
}
......
}
#Embeddable
class MobileDeviceDataId implements Serializable {
#Column(name="clientid")
private int clientId;
#Column(name="flagid")
private int flagId;
}
My Controller code looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
// code here
}
Originally I had my Entity class with just one primary key #ID on the clientId and it worked great. I would make a REST call and it would populate the MobileDeviceData class as expected. Then I switched to a composite ID using the #Embeddable and #EmbeddableId annotation and now the #RequestMapping is unable to populate the flagId parameter. Now when I make a REST call I get a null pointer exception for mobileDeviceDataId, thus its unable to update that field when it gets called and Throws a null pointer exception.
So my question is, how do I get an instance of the #Embeddable class? Can I just create one with new? I'm not sure of the implications of this since Spring may be expecting to make that value itself? What is the "normal" way this field would get updated via RequestMapping?
First of all you should avoid embedded id's, it just makes all things harder
surrogate primary keys are just easier to use, when you have a foreign key on a table with multi-column primary key it makes it much more complicated to deal with
now you faced these problems by yourself but according to your question
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
for(MobileDeviceData mobileDeviceData : deviceInfoList){
int clientId = mobileDeviceData.getMobileDeviceDataId().getClientId();
int flagId = mobileDeviceData.getMobileDeviceDataId().getFlagId();
MobileDeviceData foundMobileDeviceData = mobileDeviceDataService.findByClientIdAndFlagId(clientId, flagId);
if(foundMovileDeviceData == null){
mobileDeviceDataService.save(mobileDeviceData);
}else {
//update foundMobileDeviceData with mobileDeviceData fields
mobileDeviceDataService.save(foundMobileDeviceData);
}
}
}
else if you want to update just flag id
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
for(MobileDeviceData mobileDeviceData : deviceInfoList){
int clientId = mobileDeviceData.getMobileDeviceDataId().getClientId();
MobileDeviceData foundMobileDeviceData = mobileDeviceDataService.findByClientId(clientId);
if(foundMovileDeviceData == null){
mobileDeviceDataService.save(mobileDeviceData);
}else {
//update foundMobileDeviceData with mobileDeviceData
int flagId = foundMobileDeviceData.getMobileDeviceDataId().getFlagId();
MobileDeviceDataId mobileDeviceDataId = foundMobileDeviceData.getMobileDeviceDataId();
mobileDeviceDataId.setFlagId(mobileDeviceData)
mobileDeviceDataService.save(foundMobileDeviceData);
}
}
}
Next if you want to find something by client id just create a JPA Query like
"from MobileDeviceData WHERE mobileDeviceDataId.clientId = :clientId"
or native sql
"SELECT * FROM reporteddevicedata WHERE client_id = :someParam"
EXAMPLE JSON Request
[ {
"mobileDeviceDataId" : {
"clientId" : 0,
"flagId" : 0
},
"activityDetectedDate" : null
}, {
"mobileDeviceDataId" : {
"clientId" : 1,
"flagId" : 1
},
"activityDetectedDate" : null
}, {
"mobileDeviceDataId" : {
"clientId" : 2,
"flagId" : 2
},
"activityDetectedDate" : null
} ]
uglified ready to copy/paste version :
[{"mobileDeviceDataId":{"clientId":0,"flagId":3},"activityDetectedDate":null},{"mobileDeviceDataId":{"clientId":1,"flagId":1},"activityDetectedDate":null},{"mobileDeviceDataId":{"clientId":2,"flagId":2},"activityDetectedDate":null}]
Additionaly there should be some validation added on your MobileData object to avoid nullpointers when an invalid json request is send (with no mobileDeviceDataId present)
Finally answering the question:
Its not considered a good practice to use database model as container to share between API (because of primary keys, maybe some sensitive data, it depends).
Moreover if you want your embeddableId to work, request have to be build like in the example json. Proper fields have to be filled out. When requests arent build that way and they are just flat json without 'embedded id' you have to create some wrapper wich will fit the json format(this wrapper will be the requestbody class or List). Nextly you will have to convert wrapper to your db object with embedded id(created with a new keyword).
And this is why i suggest you not to use the composite or embedded id. This is simple example where do you have just one table, but when it comes to use foreign keys and multicolumn primary keys, the tabels are getting more complicated and messy, you make searching over db harder and this is why i suggest you to use surrogate id's without embedding anything, it makes things harder and is just messy
I'm doing a simple little grails app and decided on Shiro for signup/security and I've run into a (probably silly) problem.
I've generated the User (and Realm) class, and then extended the User to have a one-to-many association with Posts (ie the User can write, eg, blog entries, is the idea). But how do I get the Domain object from the Shiro subject?
I've tried the following:
def currentUser = SecurityUtils.getSubject()
def posts = Post.findByUser(currentUser)
But that gives me: "Message: No converter found capable of converting from type org.apache.shiro.web.subject.support.WebDelegatingSubject to type com.lordfoom.challengetrackr.User"
The domain classes are as follows:
class User {
String username
String passwordHash
static hasMany = [ roles: Role, permissions: String, posts: Post ]
static constraints = {
username(nullable: false, blank: false, unique: true)
}
}
class Post {
String title;
String body;
static belongsTo = [user:User]
static constraints = {
title(nullable:false, blank: false, unique: true)
user(unique:true)
}
}
Is there a simple way to get from the Shiro Subject to the currently logged in user's domain object? Or do I have to look it up somehow?
Any help appreciated.
If I am understanding this correctly, you just want to retrieve the user object for the user who is currently signed in, yes?
The way that I usually achieve this is by setting up a UserService containing two methods. Then I can implement getLocalUser() throughout the application.
import org.apache.shiro.SecurityUtils
class UserService {
/**
* for currently logged in user
*/
def getLocalUserId(){
def userName = SecurityUtils.subject?.principal
User.findByUsername(userName)
}
User getLocalUser(){
getLocalUserId()
}
}
Hope this helps.