I was unable to find a complete example for designing a ManyToMany relationship with Play-2.2 in Java.
My Problem is, i can't get the Form-to-Model binding to work on my ManyToMany Relationship...
There are many users. A user can have many jobs.
There are many jobs. A job can "have" many members/participants(users).
Job.java
#Entity
public class Job extends TimmeeyModel {
#Constraints.Required
#Column(unique = true)
public String jobName;
#ManyToMany(mappedBy="jobs")
public List<User> members = new ArrayList<User>();
public static Finder<Long,Job> find = new Finder<Long, Job>(Long.class,Job.class);
#Override
public Job create(){
this.save();
return this;
}
#Override
public Job update(Long id){
Job oldJob = find.byId(id);
oldJob.setJobName(this.getJobName());
oldJob.setMembers(this.getMembers());
oldJob.save();
oldJob.refresh();
return oldJob;
}
#Override
public void delete(){
this.delete();
}
public String toString(){
String userString ="";
for (User user : members) {
userString+=user.getUsername() +", ";
}
return jobName + ", " + id +" " + userString;
}
}
User.java
#Entity
public class User extends TimmeeyModel {
#Constraints.Required
#Column(unique = true)
public String username;
#ManyToMany(cascade=CascadeType.PERSIST)
public List<Job> jobs = new ArrayList<Job>();
public static Finder<Long, User> find = new Finder<Long, User>(Long.class, User.class);
public String getEmail(){
return this.username + "#pool.math.tu-berlin.de";
}
#Override
public User create(){
//jobs.add(Job.find.all().get(0));
this.save();
return this;
}
#Override
public User update(Long id){
User user = find.ref(id);
user.setUsername(this.getUsername());
user.setUsername(this.getUsername());
user.save();
user.refresh();
return user;
}
public void delete(){
this.delete();
}
public String toString(){
String jobString = "";
for (Job job : jobs) {
jobString += job.toString() + ", ";
}
return "User Id: " + id + " "+username + "Jobs: " + jobString;
}
UserController.java
public static Result showCreate(){
return ok(views.html.user.add.render(Job.find.all(), userForm));
}
public static Result create(){
Map<String, String> newData = new HashMap<String, String>();
Form<User> filledForm = userForm.bind(newData);
if(filledForm.hasErrors()) {
return badRequest("Leider nein" + filledForm.toString());
} else {
String logString = filledForm.toString();
User user = filledForm.get();
user.create();
return ok(user.toString() + logString);
}
}
The view scala template
#form(routes.Users.create()) {
#inputText(userForm("username"))
#for((value,index) <- jobs.zipWithIndex ) {
<input type="checkbox" name="jobs[]" value="#value.getId()">#value.getJobName()</input>
}
i just cant get it to work. After submitting the Joblist of a user stays empty. But when i set the list "hardcoded" inside the user object, it stays there and works like expected. SO i don't think it is related to some db/config issue. SO i suspect there is something wrong while binding the form-data to the User-Model.
I also tried different version of the form like
#value.getJobName()
#value.getJobName()
#value.getJobName()
#value.getJobName()
Nothing works. So has anyone an idea what i doing wrong, or what i don't get about play!? Maybe it is impossible to do this without writing HUGE ammounts of boilerplate code in every Controller where i want to handle models with manyToMany relationships?
Because i know i could parse the form data in the controller "by hand" and force-put the jobs-List into the users. But this can not be the right way for such a MVC framework like play :-)
Thank you
By the way, there is not even ONE example/sample in the Play-Documentation/GitHub where the term "ManyToMany" occurs...
Really i just need some not totally fucked way to handle ManyToMany Relations ( if i can't use checkboxes.. Okay, if i should use something else, OK.... I just need some way for it to work)
I answered this question myself. In short: It is NOT possible with Play! to autobind Objects from their identifiers to a List.
Maybe you will read something about custom Databinder or some magical form helpers.
The CustomDatabinder is good when you want to get a SINGLE complex Object mapped for example
User.java
public Job mainJob;
If you now enter the id of a Job into the User form, a custom Databinder is able to bind the data (if you provided one).
But this will not work for List
In fact the only thing is to use a little boilerplate code.
I did it that way
User user = filledForm.get();
List<Job> jobs = new ArrayList<Job>();
for (Job job : user.getJobs()) {
jobs.add(Job.find.byId(job.getId()));
}
user.create();
The form binder will generate a List of Jobs for the jobs List in the User. But these jobs are just "stubs" i had too iterate through the generated Jobs, and build a list with the real ones by searching for them in the Database..
It's not pretty, but also not too ugly.
Maybe this explanation can help others.
Related
I'm trying to map DTOs to entities. I created a service that only takes care of mapping objects - ObjectMapper. DTO objects have relationships with each other. When I map a single object, for example when I create User, Group, Note, everything works. But when I want to use a method that returns a Note with a specific ID - /notes/{id}, I get the following error.
Handler dispatch failed; nested exception is java.langStackOverflowError] with root cause
To get specific Note, I need to use this mapping method that also cause this error. As u can see, I have to also convert Group and Tags.
//Note
public NoteDTO NoteEntityToDtoGet(Note note) {
NoteDTO noteDTO = new NoteDTO();
noteDTO.setId(note.getId());
noteDTO.setTitle(note.getTitle());
noteDTO.setDescription(note.getDescription());
noteDTO.setGroup(GroupEntityToDtoGet(note.getGroup()));
noteDTO.setTags(TagConvertSet(note.getTags()));
return noteDTO;
}
When I don't have relationships defined as another DTO in the DTO class, but as an entity, everything works, since I don't have to convert the DTO to an entity.
Do you know where I'm making a mistake when mapping? Am I making a mistake in mapping multiple objects at once?
ObjectMapper
#Service
public class ObjectMapper {
//User
public UserDTO UserEntityToDtoGet(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setName(user.getName());
userDTO.setEmail(user.getEmail());
userDTO.setGroup(user.getGroups());
return userDTO;
}
private UserCreationDTO UserEntityToDtoCreate(User user) {
UserCreationDTO userCreationDTO = new UserCreationDTO();
userCreationDTO.setName(user.getName());
userCreationDTO.setEmail(user.getEmail());
return userCreationDTO;
}
private User UserDtoToEntityCreate(UserCreationDTO userCreationDTO) {
User user = new User();
user.setName(userCreationDTO.getName());
user.setEmail(userCreationDTO.getEmail());
return user;
}
//Group
public GroupDTO GroupEntityToDtoGet(Group group) {
GroupDTO groupDTO = new GroupDTO();
groupDTO.setId(group.getId());
groupDTO.setName(group.getName());
groupDTO.setUser(UserEntityToDtoGet(group.getUser()));
groupDTO.setNotes(NoteConvertList(group.getNotes()));
groupDTO.setTags(TagConvertSet(group.getTags()));
return groupDTO;
}
public GroupCreationDTO GroupEntityToDtoCreate(Group group) {
GroupCreationDTO groupCreationDTO = new GroupCreationDTO();
groupCreationDTO.setName(group.getName());
groupCreationDTO.setUser(UserEntityToDtoGet(group.getUser()));
groupCreationDTO.setTags(TagConvertSet(group.getTags()));
return groupCreationDTO;
}
public Group GroupDtoToEntityCreate(GroupCreationDTO groupCreationDTO) {
Group group = new Group();
group.setName(groupCreationDTO.getName());
return group;
}
//Note
public NoteDTO NoteEntityToDtoGet(Note note) {
NoteDTO noteDTO = new NoteDTO();
noteDTO.setId(note.getId());
noteDTO.setTitle(note.getTitle());
noteDTO.setDescription(note.getDescription());
noteDTO.setGroup(GroupEntityToDtoGet(note.getGroup()));
noteDTO.setTags(TagConvertSet(note.getTags()));
return noteDTO;
}
public Note NoteDtoToEntityCreate(NoteCreationDTO noteCreationDTO) {
Note note = new Note();
note.setTitle(noteCreationDTO.getTitle());
note.setDescription(noteCreationDTO.getDescription());
return note;
}
public NoteCreationDTO NoteEntityToDtoCreate(Note note) {
NoteCreationDTO noteCreationDTO = new NoteCreationDTO();
noteCreationDTO.setTitle(note.getTitle());
noteCreationDTO.setDescription(note.getDescription());
return noteCreationDTO;
}
public List<NoteDTO> NoteConvertList(List<Note> note) {
return note.stream()
.map(this::NoteEntityToDtoGet)
.collect(Collectors.toList());
}
//Tag
public TagDTO TagEntityToDtoGet(Tag tag) {
TagDTO tagDTO = new TagDTO();
tagDTO.setId(tag.getId());
tagDTO.setName(tag.getName());
tagDTO.setNotes(tag.getNotes());
tagDTO.setGroups(tag.getGroups());
return tagDTO;
}
public TagCreationDTO TagEntityToDtoCreate(Tag tag) {
TagCreationDTO tagCreationDTO = new TagCreationDTO();
tagCreationDTO.setId(tag.getId());
tagCreationDTO.setName(tag.getName());
tagCreationDTO.setNotes(tag.getNotes());
return tagCreationDTO;
}
public Set<TagDTO> TagConvertSet(Set<Tag> groups) {
return groups.stream()
.map(this::TagEntityToDtoGet)
.collect(Collectors.toSet());
}
}
You get StackOverFlowError because you end up with infinite recursive methods call and your application creates infinite amount of objects, so you just run out of memory:
1) your NoteEntityToDtoGet method gets Note's group and calls GroupEntityToDtoGet method on the Group object;
2) in GroupEntityToDtoGet method you get all Group's notes and call NoteConvertList method on them, which calls NoteEntityToDtoGet on each of the 'Note'
3) step 1 again...
... the same cycle goes over and over without a stop until your stack memory, you know, overflows :)
So you should decide do your DTO classes really need to hold references to other entity collections.
I'm trying to write a Client class which can look up entities from an external data store. The main parameter used to look up entities is a key. The problem is that sometimes a key is just a userId, sometimes it's a combination of userId and productId. So I'd like to enforce the consumer of my Client API to use a key builder specific to an entity. I tried the code below:
import java.util.function.BiFunction;
import java.util.function.Function;
public class Client {
public Function getBuilder(SomeEnum type) {
switch (type) {
case TYPE_X:
BiFunction<String, String, Entity> builder = (userId, productId) -> {
String key = userId + "-" + productId;
// lookup something in external data store based on the key and return the found entity
};
return builder;
case TYPE_Y:
Function<String, Entity> b = appId -> {
String key = userId;
// lookup something in external data store based on the key and return the found entity
};
return b;
}
}
}
The code doesn't compile of course because the return types don't match. I'm wondering if the code can be fixed and if not what is the correct Java pattern to enforce such builders with different signatures.
I would create a KeyBuilder interface as follows, and implement it differently for each type. No enums needed:
public interface KeyBuilder {
String buildKey();
}
// things that use userId & productId:
public class Foo implements KeyBuilder {
// ...
public String buildKey() { return userId + "-" + productId; }
}
// things that use appId {
public class Bar implements KeyBuilder {
// ...
public String buildKey() { return appId; }
}
Then, your code becomes cleaner and is still easy to test
// before: have to build explicitly
lookupSomethingExternal(foo.getUserId() + "-" + foo.getProductId());
lookupSomethingExternal(bar.getAppId());
// after: lookupSomethingInternal expects something implementing KeyBuilder
lookupSomethingExternal(foo);
lookupSomethingExternal(bar);
// after: can mock for unit-tests
lookupSomethingExternal(() -> "testKey");
I have an abstract class called sessions. Lectures and tutorials extend sessions. Then I have a class called enrollment which holds a list of sessions (Lectures & tutorials). How can I loop through the session list in Enrolment and return a list of Lectures only from the session list?
My next question is should I instead store 2 lists. One list of Lectures and one list of Tutorials, instead of 1 session list? This is because the sessions list is useless to me and I have to loop through it each time to get information about lectures and tutorials. Is there a way I am missing to get all the lectures objects? I am new to java.
public class Enrolment {
private List<Session> sessions;
public Enrolment() {
this.sessions = new ArrayList<>();
}
public addSession(Session session) {
this.sessions.add(session);
}
}
public class Session {
private int time;
public Session(int time) {
this.time = time;
}
}
public class Lecture extends Session {
private String lecturer;
public Lecture(int time, String lecturer) {
super(time);
this.lecturer = lecturer;
}
}
public class Tutorial extends Session {
private String tutor;
private int tutorScore;
public Tutorial(int time, String tutor, int tutorScore) {
super(time);
this.tutor = tutor;
this.tutorScore = tutorScore;
}
}
public class test {
public static void main(String[] args) {
Enrolment newEnrolment = new Enrolment();
Lecture morningLec = new Lecture(900, "Dr. Mike");
newEnrolment.addSession(morningLec);
Tutorial afternoonTut = new Tutorial(1400, "John Smith", 3);
newEnrolment.addSession(afternoonTut);
Lecture middayLec = new Lecture(1200, "Mr. Micheals");
newEnrolment.addSession(middayLec);
Tutorial NightTut = new Tutorial(1900, "Harry Pauls", 4);
newEnrolment.addSession(NightTut);
}
}
Stream the sessions list and use instanceof to filter the Lectures type objects
List<Lecture> l = sessions.stream()
.filter(Lecture.class::isInstance)
.map(Lecture.class::cast)
.collect(Collectors.toList());
By using for loop use two different lists for each type
List<Lecture> l = new ArrayList<>();
List<Tutorial> t = new ArrayList<>();
for (Session s : sessions) {
if (s instanceof Lecture) {
l.add((Lecture) s);
}
else if(s instanceof Tutorial) {
t.add((Tutorial) s);
}
}
Maybe you should store in two lists, just like:
public class Enrolment {
private List<Lecture> lectures;
private List<Tutorial> tutorials;
public Enrolment() {
this.lectures = new ArrayList<>();
this.tutorials = new ArrayList<>();
}
public void addSession(Session session) {
if (session instanceof Lecture) {
lectures.add((Lecture) session);
} else if (session instanceof Tutorial) {
tutorials.add((Tutorial) session);
}
}
public List<Lecture> getLectures() {
return lectures;
}
public List<Tutorial> getTutorials() {
return tutorials;
}
public List<Session> getAllSessions() {
ArrayList<Session> sessions = new ArrayList<>(lectures);
sessions.addAll(tutorials);
return sessions;
}
}
Is that what you need?
My next question is should I instead store 2 lists. One list of
Lectures and one list of Tutorials, instead of 1 session list? This is
because the sessions list is useless to me and I have to loop through
it each time to get information about lectures and tutorials. Is there
a way I am missing to get all the lectures objects?
You answered yourself to your problem.
When you start to write too complex/boiler plate code to make things that should be simple such as iterating on a list of objects that you have just added, it is a sign that you should step back and redesign the thing.
By introducing Enrolment.addSession(Session session),
you introduced an undesirable abstraction :
public class Enrolment {
private List<Session> sessions;
public Enrolment() {
this.sessions = new ArrayList<>();
}
public addSession(Session session) {
this.sessions.add(session);
}
}
You don't want to handle uniformally Lecture and Tutorial from the Enrolment point of view, so just don't merge them in the same List only because these rely on the same interface (Session).
Abstraction has to be used when it is required and not systematically because that is possible.
Don't you add all objects in a List of Object because all is Object ? No.
Instead of, create this distinction both from the API method and from its implementation :
public class Enrolment {
private List<Conference> conferences = new ArrayList<>();
private List<Tutorial> tutorials = new ArrayList<>();
public addConference(Conference conference) {
this.conferences.add(conference);
}
public addTutorial(Tutorial tutorial) {
this.tutorials.add(tutorial);
}
}
And use it :
Lecture morningLec = new Lecture(900, "Dr. Mike");
newEnrolment.addLecture(morningLec);
Tutorial afternoonTut = new Tutorial(1400, "John Smith", 3);
newEnrolment.addTutorial(afternoonTut);
Note that you could have a scenario where you need to manipulate uniformally Tutorial and Lecture for some processings but that for others you want to distinguish them.
In this case, you have some common ways :
instanceOf : easy to use but also easy to make a code brittle. For example, later you could add a new subclass in the Session hierarchy and without be aware of it, instances of this subclass could be included or excluded in some processing without that the compiler warns you.
provide a abstract method that returns a boolean or an enum to convey the nature of the object (ex: isLecture()). More robust than instanceOf since the compiler constraints you to override the method but it may also lead to error prone code if multiple subclasses are added and that the filters are not only on Lecture but Lecture and another type. So I would favor this way while the filtering condition stays simple.
define three lists : one for lectures, another for conferences and another that contains all of these that should be handled uniformally. More complex way but more robust way too. I would favor it only for cases with complex/changing filtering conditions.
List<Lecture> l = newEnrolment.getSessions()
.stream()
.filter(s-> s.getClass().equals(Lecture.class))
.map(session -> (Lecture) session)
.collect(Collectors.toList());
!!! Don't use typeof
Quoting from your question,
This is because the sessions list is useless to me.
So, this is probably not the right place to have the list(?).
My preference would be to have
public interface Enrolment {
public abstract addSession(Session session);
public abstract getSessions();
}
And List<LectureEnrolment> and List<TutorialEnrolment> must be in their respective classes.(I have renamed Lecture to LectureEnrolment and Tutorial to TutorialEnrolment)
main() must have something like,
Enrolment lectureEnrolment= new LectureEnrolment()
Enrolment tutorialEnrolement = new TutorialEnrolment()
call the respective addSession() or getSession() depending on requirement.
change private List<Session> sessions; to public List<Session> sessions; in class Enrolment
public static void main(String[] args) {
....
var lecturesAndTutorials = newEnrolment.sessions.where(x => x.getType() == typeof(Lecture) || x.getType() == typeof(Tutorial));
....
}
My problem lies in the fields of JsonSerialization as implemented in Jackson FasterXML Library. I have a series of endpoints through which I exchange content between my back-end and a MVVM front-end framework. This is working, but now I am a little stuck as I got to the point where I want to handle user creation/registration.
This is the model (entity) that represents a group in my application (I omit irrelevant import declarations and JPA annotations):
#JsonRootName(value="userGroup")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Groups extends MetaInfo implements Serializable {
private String groupName;
private Set<Credential> credentials = new HashSet<>();
public Groups() {
super();
}
public Groups(String groupName) {
this();
this.groupName = groupName;
}
public Groups(String createdBy, String groupName) {
this();
setCreatedBy(createdBy);
this.groupName = groupName;
}
#JsonGetter("group_Name")
// #JsonValue
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
updateModified();
}
#JsonIgnore
public Set<Credential> getCredentials() {
return credentials;
}
public void setCredentials(Set<Credential> credentials) {
this.credentials = credentials;
}
public void addCredential(Credential c) {
credentials.add(c);
if (c.getGroup() != this) {
c.setGroup(this);
}
}
#Override
public String toString() {
return "Groups{" + "groupName=" + groupName + '}';
}
}
And this is the method in the endpoint that retrieves (if exists) and returns a serialized version of a Groups to a JavaScript client:
#Path("/groups")
#Produces("application/json")
public class GroupsResourceService extends RimmaRestService{
#Inject
#Production
private GroupsRepository groupsRepository;
...
#GET
#Path("{group}")
public Response getGroup(#PathParam("group") String group){
if(InputValidators.stringNotNullNorEmpty.apply(group)){
//proceed with making call to the repo
Optional<Groups> optGroup = ofNullable(groupsRepository.getByGroupName(group));
if(optGroup.isPresent()){//ultimately success scenario
try {
String output = getMapper().writeValueAsString(optGroup.get());
return Response.ok(output).build();
} catch (JsonProcessingException e) {
logger.error("Serialization error: "+e.getMessage()+
"\n"+e.getClass().getCanonicalName());
throw new InternalServerErrorException("Server error "
+ " serializing the requested group \""+group+"\"");
}
} else{
throw new NotFoundException("Group " + group + " could not be found");
}
}else{//empty space after the slash
throw new BadRequestException("You haven't provided a group parameter",
Response.status(Response.Status.BAD_REQUEST).build());
}
}
}
Trying to test this code like this:
#Test
public void successfullResponse(){
Response success = groupsResourceService.getGroup("admin");
assertTrue(success.getStatus()==200);
}
...cruelly fails:
<< ERROR!
javax.ws.rs.InternalServerErrorException: Server error serializing the requested group "admin"
at com.vgorcinschi.rimmanew.rest.services.GroupsResourceService.getGroup(GroupsResourceService.java:54)
at com.vgorcinschi.rimmanew.services.GroupsResourceServiceTest.successfullResponse(GroupsResourceServiceTest.java:48)
In this case stack trace is of 0 help, though - that's why I am pasting the output of the log that catches the underlying Json exception:
15:05:05.857 [main] ERROR com.vgorcinschi.rimmanew.rest.services.GroupsResourceService - Serialization error: Can not write a field name, expecting a value
com.fasterxml.jackson.core.JsonGenerationException
Having visited and analyzed 13 similar complaints links (of which 3 are from stackoverflow) I came up with a solution which is more a workaround - if you look back at the entity, I have commented #JsonValue. If I uncomment that and comment #JsonGetter("group_Name") then the test passes with the following output:
{"userGroup":"admin"}
This being only a workaround, I decided to recur to asking for help which I hope someone will be able and kind enough to provide.
I have 2 domain objects, User and SystemRights (It's a many to many, so 1 user can have many rights and 1 right can be owned by many users). I'm looking for a simple way to check if a user has the required rights.
User Domain
class User {
static hasMany = [systemRights: SystemRight, enterpriseUsers: EnterpriseUser]
String email;
String passwordHash;
}
SystemRight Domain
class SystemRight {
public static final String LOGIN = "LOGIN"
public static final String MODIFY_ALL_ENTERPRISES = "MODIFY_ALL_ENTERPRISES"
public static final String ADMINISTER_SYSTEM = "ADMINISTER_SYSTEM"
public static final String VALIDATE_SUPPLIER_SCORECARDS = "VALIDATE_SUPPLIER_SCORECARDS"
static hasMany = [users:User]
static belongsTo = User
String name
}
The following did not work for me:
In User.class
public boolean hasRights(List<String> requiredRights) {
def userHasRight = SystemRight.findByUserAndSystemRight (this, SystemRight.findByName(requiredRight));
// Nor this
def userHasRight = this.systemRights.contains(SystemRight.findByName(requiredRight));
}
Current Horrible Solution
public boolean hasRights(List<String> requiredRights) {
for (String requiredRight : requiredRights) {
def has = false
for (SystemRight userRight : user.systemRights) {
if (userRight.name == requiredRight) {
has = true
break;
}
}
if (has == false) {
return false;
}
}
return true
}
If you're able/willing to change things up a bit, I'd highly recommend doing the following. It will make you're life so much easier.
First, remove the hasMany for SystemRight and User from both Domains and remove the belongsTo User from SystemRight.
Next, create the Domain to represent the join table.
class UserSystemRight {
User user
SystemRight systemRight
boolean equals(other) {
if (!(other instanceof UserSystemRight)) {
return false
}
other.user?.id == user?.id && other.systemRight?.id == systemRight?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (systemRight) builder.append(systemRight.id)
builder.toHashCode()
}
// add some more convenience methods here if you want like...
static UserSystemRight get(long userId, long systemRightId, String systemRightName) {
find 'from UserSystemRight where user.id=:userId and systemRight.id=:systemRightId and systemRight.name=:systemRightName',
[userId: userId, systemRightId: systemRightId, systemRightName: systemRightName]
}
}
Then, in your User class you can add this method:
Set<SystemRight> getSystemRights() {
UserSystemRight.findAllByUser(this).collect { it.systemRight } as Set
}
Then, add this to your SystemRight Domain:
Set<User> getUsers() {
UserSystemRight.findAllBySystemRight(this).collect { it.user } as Set
}
For a more detailed explenation of why this approach is full of win, aside from actually solving your problem, take a gander at this.
I would definitely try to solve this in the database.
def relevantUserRights = SystemRight.withCriteria {
eq("user", this)
"in"("name", requiredRights);
}
return relevantUserRights.size() == requiredRights.size()
How about the following?
public boolean hasRights(List<String> requiredRights) {
return null != (this.systemRights.find { requiredRights.contains(it) });
}
(Not tested: Groovy newbie here)