From 2d1eac707394efccd3c38fe4f15da20539286b21 Mon Sep 17 00:00:00 2001 From: Arimihanta Date: Sun, 11 Jul 2021 15:57:08 +0300 Subject: [PATCH 1/4] Vote majoritaire --- .../java/fr/lirmm/aren/model/vm/VMChoice.java | 311 ++++++++++++++++++ .../java/fr/lirmm/aren/model/vm/VMTheme.java | 153 +++++++++ .../java/fr/lirmm/aren/model/vm/VMVote.java | 108 ++++++ .../lirmm/aren/service/AbstractService.java | 36 ++ .../service/framadate/FDThemeService.java | 4 +- .../aren/service/vm/VMChoiceService.java | 67 ++++ .../lirmm/aren/service/vm/VMThemeService.java | 100 ++++++ .../lirmm/aren/service/vm/VMVoteService.java | 86 +++++ .../aren/ws/rest/VMChoiceRESTFacade.java | 88 +++++ .../lirmm/aren/ws/rest/VMThemeRESTFacade.java | 136 ++++++++ .../lirmm/aren/ws/rest/VMVoteRESTFacade.java | 79 +++++ 11 files changed, 1166 insertions(+), 2 deletions(-) create mode 100644 src/main/java/fr/lirmm/aren/model/vm/VMChoice.java create mode 100644 src/main/java/fr/lirmm/aren/model/vm/VMTheme.java create mode 100644 src/main/java/fr/lirmm/aren/model/vm/VMVote.java create mode 100644 src/main/java/fr/lirmm/aren/service/vm/VMChoiceService.java create mode 100644 src/main/java/fr/lirmm/aren/service/vm/VMThemeService.java create mode 100644 src/main/java/fr/lirmm/aren/service/vm/VMVoteService.java create mode 100644 src/main/java/fr/lirmm/aren/ws/rest/VMChoiceRESTFacade.java create mode 100644 src/main/java/fr/lirmm/aren/ws/rest/VMThemeRESTFacade.java create mode 100644 src/main/java/fr/lirmm/aren/ws/rest/VMVoteRESTFacade.java diff --git a/src/main/java/fr/lirmm/aren/model/vm/VMChoice.java b/src/main/java/fr/lirmm/aren/model/vm/VMChoice.java new file mode 100644 index 0000000..5f6e92e --- /dev/null +++ b/src/main/java/fr/lirmm/aren/model/vm/VMChoice.java @@ -0,0 +1,311 @@ +package fr.lirmm.aren.model.vm; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import fr.lirmm.aren.model.AbstractEntity; +import org.hibernate.annotations.SortNatural; +import org.hibernate.annotations.Type; + +import javax.persistence.*; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.time.ZonedDateTime; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 24/06/2021 + * @project aren-1 + */ +@Entity +@Table(name = "vm_choices") +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMChoice.class) +public class VMChoice extends AbstractEntity implements Serializable { + + @JoinColumn(name = "themeId", referencedColumnName = "id") + @ManyToOne(optional = false) + private VMTheme themeId; + + @Size(max = 255) + @Column(name = "title") + private String title; + + @Lob + @Type(type = "org.hibernate.type.TextType") + @Column(name = "description") + private String description; + + @Size(max = 1024) + @Column(name = "url") + private String url; + + @Lob + @Type(type = "org.hibernate.type.TextType") + @Column(name = "img") + private String img; + + @Column(name = "rejected") + private int rejected=0; + + @Column(name = "insufficient") + private int insufficient=0; + + @Column(name = "pass") + private int pass=0; + + @Column(name = "acceptable") + private int acceptable=0; + + @Column(name = "good") + private int good=0; + + @Column(name = "very_good") + private int veryGood=0; + + @Column(name = "excellent") + private int excellent=0; + + @Column(name = "createdAt") + private ZonedDateTime createdAt=ZonedDateTime.now(); + + + @OneToMany(mappedBy = "subThemeId",fetch = FetchType.LAZY) + @SortNatural + private SortedSet votes = new TreeSet<>(); + + /** + * + * @return + */ + public VMTheme getThemeId() { + return themeId; + } + + /** + * + * @param themeId + */ + public void setThemeId(VMTheme themeId) { + this.themeId = themeId; + } + + /** + * + * @return + */ + public String getTitle() { + return title; + } + + /** + * + * @param title + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * + * @return + */ + public String getDescription() { + return description; + } + + /** + * + * @param description + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * + * @return + */ + public String getUrl() { + return url; + } + + /** + * + * @param url + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * + * @return + */ + public String getImg() { + return img; + } + + /** + * + * @param img + */ + public void setImg(String img) { + this.img = img; + } + + /** + * + * @return + */ + public int getRejected() { + return rejected; + } + + /** + * + * @param rejected + */ + public void setRejected(int rejected) { + this.rejected = rejected; + } + + /** + * + * @return + */ + public int getInsufficient() { + return insufficient; + } + + /** + * + * @param insufficient + */ + public void setInsufficient(int insufficient) { + this.insufficient = insufficient; + } + + /** + * + * @return + */ + public int getPass() { + return pass; + } + + /** + * + * @param pass + */ + public void setPass(int pass) { + this.pass = pass; + } + + /** + * + * @return + */ + public int getAcceptable() { + return acceptable; + } + + /** + * + * @param acceptable + */ + public void setAcceptable(int acceptable) { + this.acceptable = acceptable; + } + + /** + * + * @return + */ + public int getGood() { + return good; + } + + /** + * + * @param good + */ + public void setGood(int good) { + this.good = good; + } + + /** + * + * @return + */ + public int getVeryGood() { + return veryGood; + } + + /** + * + * @param veryGood + */ + public void setVeryGood(int veryGood) { + this.veryGood = veryGood; + } + + /** + * + * @return + */ + public int getExcellent() { + return excellent; + } + + /** + * + * @param excellent + */ + public void setExcellent(int excellent) { + this.excellent = excellent; + } + + /** + * + * @return + */ + public ZonedDateTime getCreatedAt() { + return createdAt; + } + + /** + * + * @param createdAt + */ + public void setCreatedAt(ZonedDateTime createdAt) { + this.createdAt = createdAt; + } + + /** + * + * @return + */ + public SortedSet getVotes() { + return votes; + } + + /** + * + * @param votes + */ + public void setVotes(SortedSet votes) { + this.votes = votes; + } + + public boolean isVoted(){ + if(this.getRejected()!=0 || + this.getInsufficient()!=0 || + this.getPass()!=0 || + this.getAcceptable()!=0 || + this.getGood()!=0 || + this.getVeryGood()!=0 || + this.getExcellent()!=0 + ) return true ; + return false ; + } +} diff --git a/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java b/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java new file mode 100644 index 0000000..7befdfa --- /dev/null +++ b/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java @@ -0,0 +1,153 @@ +package fr.lirmm.aren.model.vm; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import fr.lirmm.aren.model.AbstractEntity; +import fr.lirmm.aren.model.User; +import org.hibernate.annotations.Type; + +import javax.persistence.*; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.time.ZonedDateTime; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 24/06/2021 + * @project aren-1 + */ +@Entity +@Table(name = "vm_themes") +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMTheme.class) +public class VMTheme extends AbstractEntity implements Serializable { + + /** + * + */ + @JoinColumn(name = "author", referencedColumnName = "id") + @ManyToOne + private User author; + + + @Size(max = 255) + @Column(name = "title") + private String title; + + @Lob + @Type(type = "org.hibernate.type.TextType") + @Column(name = "description") + private String description; + + @Column(name = "createdAt") + private ZonedDateTime createdAt=ZonedDateTime.now(); + + @Column(name = "expiracyDate") + private ZonedDateTime expiracyDate; + + @OneToMany(mappedBy = "themeId") + private Set choices = new LinkedHashSet<>(); + + /** + * + * @return + */ + public User getAuthor() { + return author; + } + + /** + * + * @param author + */ + public void setAuthor(User author) { + this.author = author; + } + + /** + * + * @return + */ + public String getTitle() { + return title; + } + + /** + * + * @param title + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * + * @return + */ + public String getDescription() { + return description; + } + + /** + * + * @param description + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * + * @return + */ + public ZonedDateTime getCreatedAt() { + return createdAt; + } + + /** + * + * @param createdAt + */ + public void setCreatedAt(ZonedDateTime createdAt) { + this.createdAt = createdAt; + } + + /** + * + * @return + */ + public ZonedDateTime getExpiracyDate() { + return expiracyDate; + } + + /** + * + * @param expiracyDate + */ + public void setExpiracyDate(ZonedDateTime expiracyDate) { + this.expiracyDate = expiracyDate; + } + + /** + * + * @return + */ + public Set getChoices() { + return choices; + } + + /** + * + * @param choices + */ + public void setChoices(Set choices) { + this.choices = choices; + } + + @Override + public String toString(){ + return "title : "+this.title+"\n" + +"Description : "+this.description+"\n" + +"Expiration : "+this.expiracyDate+"\n"; + } +} diff --git a/src/main/java/fr/lirmm/aren/model/vm/VMVote.java b/src/main/java/fr/lirmm/aren/model/vm/VMVote.java new file mode 100644 index 0000000..f63f7e1 --- /dev/null +++ b/src/main/java/fr/lirmm/aren/model/vm/VMVote.java @@ -0,0 +1,108 @@ +package fr.lirmm.aren.model.vm; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import fr.lirmm.aren.model.AbstractEntity; +import fr.lirmm.aren.model.User; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.ZonedDateTime; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 25/06/2021 + * @project aren-1 + */ +@Entity +@Table(name = "vm_votes") +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMVote.class) +public class VMVote extends AbstractEntity implements Serializable { + public enum Opinion { + REJECTED, + INSUFFICIENT, + PASS, + ACCEPTABLE, + GOOD, + VERY_GOOD, + EXCELLENT + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "subThemeId") + private VMChoice subThemeId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "authorId") + private User authorId ; + + @Enumerated(EnumType.STRING) + @Column(name = "opinion", nullable = false) + private Opinion opinion=Opinion.REJECTED; + + @Column(name = "createdAt") + private ZonedDateTime createdAt=ZonedDateTime.now(); + + /** + * + * @return + */ + public VMChoice getSubThemeId() { + return subThemeId; + } + + /** + * + * @param subThemeId + */ + public void setSubThemeId(VMChoice subThemeId) { + this.subThemeId = subThemeId; + } + + /** + * + * @return + */ + public User getAuthorId() { + return authorId; + } + + /** + * + * @param authorId + */ + public void setAuthorId(User authorId) { + this.authorId = authorId; + } + + /** + * + * @return + */ + public Opinion getOpinion() { + return opinion; + } + + /** + * + * @param opinion + */ + public void setOpinion(Opinion opinion) { + this.opinion = opinion; + } + + /** + * + * @return + */ + public ZonedDateTime getCreatedAt() { + return createdAt; + } + + /** + * + * @param createdAt + */ + public void setCreatedAt(ZonedDateTime createdAt) { + this.createdAt = createdAt; + } +} diff --git a/src/main/java/fr/lirmm/aren/service/AbstractService.java b/src/main/java/fr/lirmm/aren/service/AbstractService.java index c52350d..7d4d15a 100644 --- a/src/main/java/fr/lirmm/aren/service/AbstractService.java +++ b/src/main/java/fr/lirmm/aren/service/AbstractService.java @@ -152,6 +152,42 @@ public abstract class AbstractService { } } + public T insert(T entity) { + // If ever the entity is attached, it needs to be detached + getEntityManager().detach(entity); + // If ever the id is set, it needs to be removed + entity.setId(null); + + try { + this.transactionBegin(); + getEntityManager().persist(entity); + getEntityManager().refresh(entity); + this.afterCreate(entity); + this.commit(); + } catch (PersistenceException e) { + if (e.getCause() instanceof PropertyValueException) { + PropertyValueException cause = (PropertyValueException) e.getCause(); + throw InsertEntityException.MANDATORY_PROPERTY(cause.getPropertyName()); + } else if (e.getCause() instanceof ConstraintViolationException) { + // Parse the error to a client readable one + Throwable cause = e.getCause().getCause(); + String details = cause.getMessage(); + Pattern pattern = Pattern.compile("\\((.*)\\)=\\((.*)\\)"); + Matcher matcher = pattern.matcher(details); + if (matcher.find()) { + String keyName = matcher.group(1); + String keyValue = matcher.group(2); + throw InsertEntityException.DUPLICATE_KEY(keyName, keyValue); + } else { + throw InsertEntityException.OTHER(details); + } + } else { + throw e; + } + } + return entity ; + } + /** * * @param entity diff --git a/src/main/java/fr/lirmm/aren/service/framadate/FDThemeService.java b/src/main/java/fr/lirmm/aren/service/framadate/FDThemeService.java index a7c671f..8f07c3d 100644 --- a/src/main/java/fr/lirmm/aren/service/framadate/FDThemeService.java +++ b/src/main/java/fr/lirmm/aren/service/framadate/FDThemeService.java @@ -40,8 +40,8 @@ public class FDThemeService extends AbstractService { super.edit(fdTheme); } - public FDTheme find(Long fdPollId, boolean withChoices, boolean withVotes) { - List results = generateQuery(fdPollId, withChoices,withVotes).getResultList(); + public FDTheme find(Long themeId, boolean withChoices, boolean withVotes) { + List results = generateQuery(themeId, withChoices,withVotes).getResultList(); if (results.isEmpty()) { throw new NotFoundException(); } diff --git a/src/main/java/fr/lirmm/aren/service/vm/VMChoiceService.java b/src/main/java/fr/lirmm/aren/service/vm/VMChoiceService.java new file mode 100644 index 0000000..5362314 --- /dev/null +++ b/src/main/java/fr/lirmm/aren/service/vm/VMChoiceService.java @@ -0,0 +1,67 @@ +package fr.lirmm.aren.service.vm; + +import fr.lirmm.aren.model.vm.VMChoice; +import fr.lirmm.aren.service.AbstractService; + +import javax.enterprise.context.ApplicationScoped; +import javax.persistence.TypedQuery; +import javax.ws.rs.NotFoundException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021 + * @project aren-1 + */ +@ApplicationScoped +public class VMChoiceService extends AbstractService { + public VMChoiceService(){super(VMChoice.class) ;} + + /** + * + * @param choiceId + * @param withVotes + * @return + */ + private TypedQuery generateQuery(Long choiceId, boolean withVotes){ + TypedQuery query = getEntityManager().createQuery("SELECT vmc " + +"FROM VMChoice vmc " + +(withVotes? "LEFT JOIN FETCH vmc.votes vo " : "") + + (choiceId != null + ? "WHERE vmc.id = :choiceId " + : "WHERE vmc.id IS NOT NULL ") + ,VMChoice.class + ) ; + if (choiceId != null) { + query.setParameter("choiceId", choiceId); + } + return query ; + } + + /** + * + * @param withVotes + * @return + */ + public Set findAll(boolean withVotes){ + return new HashSet(generateQuery(null, withVotes).getResultList()) ; + } + + /** + * + * @param choice + */ + @Override + public void edit(VMChoice choice){ + super.edit(choice); + } + + public VMChoice find(Long choiceId, boolean withVotes){ + List choices=generateQuery(choiceId,withVotes).getResultList() ; + if (choices.isEmpty()) { + throw new NotFoundException(); + } + return choices.get(0) ; + } +} diff --git a/src/main/java/fr/lirmm/aren/service/vm/VMThemeService.java b/src/main/java/fr/lirmm/aren/service/vm/VMThemeService.java new file mode 100644 index 0000000..c999509 --- /dev/null +++ b/src/main/java/fr/lirmm/aren/service/vm/VMThemeService.java @@ -0,0 +1,100 @@ +package fr.lirmm.aren.service.vm; + +import fr.lirmm.aren.model.vm.VMChoice; +import fr.lirmm.aren.model.vm.VMTheme; +import fr.lirmm.aren.service.AbstractService; + +import javax.enterprise.context.ApplicationScoped; +import javax.persistence.TypedQuery; +import javax.ws.rs.NotFoundException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021 + * @project aren-1 + */ +@ApplicationScoped +public class VMThemeService extends AbstractService { + public VMThemeService(){ + super(VMTheme.class) ; + } + private TypedQuery generateQuery(Long themeId, boolean withChoices, boolean withVotes){ + TypedQuery query = getEntityManager().createQuery("SELECT vmt " + +"FROM VMTheme vmt " + +(withChoices? "LEFT JOIN FETCH vmt.choices co " : "") + +(withVotes? "LEFT JOIN FETCH co.votes vo " : "") + + (themeId != null + ? "WHERE vmt.id = :themeId " + : "WHERE vmt.id IS NOT NULL ") + , VMTheme.class + ) ; + if (themeId != null) { + query.setParameter("themeId", themeId); + } + return query ; + } + public Set findAll(boolean withChoices, boolean withVotes) { + return new HashSet(generateQuery(null, withChoices,withVotes).getResultList()); + } + + @Override + public void edit(VMTheme theme) { + super.edit(theme); + } + + public VMTheme find(Long themeId, boolean withChoices, boolean withVotes) { + List themes = generateQuery(themeId, withChoices,withVotes).getResultList(); + if (themes.isEmpty()) { + throw new NotFoundException(); + } + return themes.get(0); + } + + /** + * + * @param themeId + */ + public void delete(Long themeId) { + this.transactionBegin(); + List choices = getEntityManager().createQuery("SELECT c " + + "FROM VMChoice c " + + "WHERE c.themeId.id = :themeId ", VMChoice.class) + .setParameter("themeId", themeId) + .getResultList(); + if (!choices.isEmpty()) { + for(VMChoice choice : choices){ + this.deleteVotes(choice.getId()); + } + } + try{ + getEntityManager().createQuery("DELETE FROM VMChoice choice " + + "WHERE choice.themeId.id = :themeId") + .setParameter("themeId", themeId) + .executeUpdate(); + + getEntityManager().createQuery("DELETE FROM VMTheme theme " + + "WHERE theme.id = :themeId") + .setParameter("themeId", themeId) + .executeUpdate(); + }catch(Exception ex){ + System.err.println("Erreur : "+ex.getMessage()); + } + + this.commit(); + } + + /** + * + * @param choiceId + */ + public void deleteVotes(Long choiceId) { + this.transactionBegin(); + getEntityManager().createQuery("DELETE FROM VMVote vote " + + "WHERE vote.subThemeId.id = :choiceId") + .setParameter("choiceId", choiceId) + .executeUpdate(); + this.commit(); + } +} diff --git a/src/main/java/fr/lirmm/aren/service/vm/VMVoteService.java b/src/main/java/fr/lirmm/aren/service/vm/VMVoteService.java new file mode 100644 index 0000000..67982f4 --- /dev/null +++ b/src/main/java/fr/lirmm/aren/service/vm/VMVoteService.java @@ -0,0 +1,86 @@ +package fr.lirmm.aren.service.vm; + +import fr.lirmm.aren.model.vm.VMChoice; +import fr.lirmm.aren.model.vm.VMVote; +import fr.lirmm.aren.service.AbstractService; + +import javax.enterprise.context.ApplicationScoped; +import javax.ws.rs.NotFoundException; +import java.util.List; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 25/06/2021 + * @project aren-1 + */ +@ApplicationScoped +public class VMVoteService extends AbstractService { + public VMVoteService(){ + super(VMVote.class) ; + } + + @Override + protected void afterCreate(VMVote entity){ + this.updateExternaleTables(entity); + } + + public void updateExternaleTables(VMVote vmVote){ + super.transactionBegin(); + + List results = getEntityManager().createQuery("SELECT c " + + "FROM VMChoice c " + + "WHERE c.id = :id", VMChoice.class) + .setParameter("id", vmVote.getSubThemeId().getId()) + .getResultList(); + + if (results.isEmpty()) { + throw new NotFoundException(); + } + + VMChoice choice=results.get(0) ; + switch(vmVote.getOpinion()){ + case REJECTED: + choice.setRejected(choice.getRejected()+1); + break ; + case INSUFFICIENT: + choice.setInsufficient(choice.getInsufficient()+1); + break; + case PASS: + choice.setPass(choice.getPass()+1); + break; + case ACCEPTABLE: + choice.setAcceptable(choice.getAcceptable()+1); + break ; + case GOOD: + choice.setGood(choice.getGood()+1); + break ; + case VERY_GOOD: + choice.setVeryGood(choice.getVeryGood()+1); + break ; + case EXCELLENT: + choice.setExcellent(choice.getExcellent()+1); + break ; + } + + getEntityManager().createQuery("UPDATE VMChoice c SET " + + "c.rejected = :rejected, " + + "c.insufficient = :insufficient, " + + "c.pass = :pass, " + + "c.acceptable = :acceptable, " + + "c.good = :good, " + + "c.veryGood = :veryGood, " + + "c.excellent = :excellent " + +"WHERE c.id=:id" + ) + .setParameter("rejected",choice.getRejected()) + .setParameter("insufficient", choice.getInsufficient()) + .setParameter("pass",choice.getPass()) + .setParameter("acceptable",choice.getAcceptable()) + .setParameter("good", choice.getGood()) + .setParameter("veryGood",choice.getVeryGood()) + .setParameter("excellent",choice.getExcellent()) + .setParameter("id", vmVote.getSubThemeId().getId()) + .executeUpdate() ; + super.commit(); + } + +} diff --git a/src/main/java/fr/lirmm/aren/ws/rest/VMChoiceRESTFacade.java b/src/main/java/fr/lirmm/aren/ws/rest/VMChoiceRESTFacade.java new file mode 100644 index 0000000..0381466 --- /dev/null +++ b/src/main/java/fr/lirmm/aren/ws/rest/VMChoiceRESTFacade.java @@ -0,0 +1,88 @@ +package fr.lirmm.aren.ws.rest; + +import fr.lirmm.aren.model.vm.VMChoice; +import fr.lirmm.aren.service.vm.VMChoiceService; +import fr.lirmm.aren.service.vm.VMVoteService; + +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.Path; +import java.util.List; +import java.util.Set; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021 + * @project aren-1 + */ +@RequestScoped +@Path("vm/choices") +public class VMChoiceRESTFacade extends AbstractRESTFacade{ + @Inject + VMChoiceService choiceService ; + + @Inject + VMVoteService voteService ; + @Override + protected VMChoiceService getService() { + return choiceService; + } + + /** + * + * @param choice + * @return + */ + @Override + //@RolesAllowed({"ADMIN"}) + @PermitAll + public VMChoice create(VMChoice choice){ + return super.create(choice) ; + } + + /** + * + * @param id of the Entity to update + * @param choice + * @return + */ + @Override + public VMChoice edit(Long id,VMChoice choice){ + return super.edit(id,choice) ; + } + + /** + * + * @param id of the Entity to remove + */ + @Override + @RolesAllowed({"ADMIN"}) + public void remove(Long id) { + super.remove(id); + } + + /** + * + * @return + */ + @Override + @PermitAll + public Set findAll() { + boolean withVotes = this.overview == null; + Set choices= choiceService.findAll(withVotes); + return choices ; + } + + /** + * + * @param id of the Entity to fetch + * @return + */ + @Override + @PermitAll + public VMChoice find(Long id) { + boolean withVotes = this.overview == null; + return choiceService.find(id,withVotes); + } +} diff --git a/src/main/java/fr/lirmm/aren/ws/rest/VMThemeRESTFacade.java b/src/main/java/fr/lirmm/aren/ws/rest/VMThemeRESTFacade.java new file mode 100644 index 0000000..0416c57 --- /dev/null +++ b/src/main/java/fr/lirmm/aren/ws/rest/VMThemeRESTFacade.java @@ -0,0 +1,136 @@ +package fr.lirmm.aren.ws.rest; + +import fr.lirmm.aren.model.vm.VMChoice; +import fr.lirmm.aren.model.vm.VMTheme; +import fr.lirmm.aren.service.vm.VMChoiceService; +import fr.lirmm.aren.service.vm.VMThemeService; +import fr.lirmm.aren.service.vm.VMVoteService; +import fr.mieuxvoter.mj.*; + +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.DELETE; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import java.util.*; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 08/07/2021 + * @project aren-1 + */ +@RequestScoped +@Path("vm/themes") +public class VMThemeRESTFacade extends AbstractRESTFacade{ + @Inject + VMThemeService themeService ; + + @Inject + VMChoiceService choiceService ; + + @Inject + VMVoteService voteService ; + + @Override + protected VMThemeService getService() { + return themeService; + } + + @Override + //@RolesAllowed({"ADMIN"}) + @PermitAll + public VMTheme create(VMTheme theme){ + System.out.println(theme.toString()); + return super.create(theme); + } + + @Override + //@RolesAllowed({"ADMIN"}) + public VMTheme edit(Long id, VMTheme theme){return super.edit(id, theme);} + + @Override + @PermitAll + //@RolesAllowed({"MODO"}) + public Set findAll() { + boolean withChoices = this.overview == null; + Set themes= themeService.findAll(withChoices, true); + /** + * Sort by voting rank + */ + + Set newThemes=new HashSet<>() ; + + + themes.forEach(theme -> { + Object []choices=theme.getChoices().toArray() ; + List choicesNotVoted=new ArrayList<>() ; + List proposalTallyInterfaces=new ArrayList<>() ; + for(int i=0 ; i setChoices = new LinkedHashSet<>(); + System.out.println("Rang : ") ; + for(int i=0 ; i{ + @Inject + VMVoteService vmVoteService ; + + /** + * + * @return + */ + @Override + protected VMVoteService getService() { + return this.vmVoteService; + } + + /** + * + * @param vote + * @return + */ + @Override + //@RolesAllowed({"USER"}) + @PermitAll + public VMVote create(VMVote vote){ + return super.create(vote) ; + } + + /** + * + * @param voteId + * @return + */ + @Override + //@RolesAllowed({"USER"}) + @PermitAll + public VMVote find(Long voteId){ + return super.find(voteId) ; + } + + /** + * + * @param id + */ + @Override + @RolesAllowed({"ADMIN"}) + public void remove(Long id) { + super.remove(id); + } + + /** + * + * @return + */ + @Override + //@RolesAllowed({"USER"}) + @PermitAll + public Set findAll(){ + return super.findAll() ; + } +} From d7c130d740547b6f97d81b40b2af2fa0b6fbb485 Mon Sep 17 00:00:00 2001 From: Arimihanta Date: Sun, 11 Jul 2021 15:57:51 +0300 Subject: [PATCH 2/4] Vote majoritaire --- .../java/fr/mieuxvoter/mj/CollectedTally.java | 72 +++++++ .../fr/mieuxvoter/mj/DefaultGradeTally.java | 47 +++++ .../mieuxvoter/mj/DeliberatorInterface.java | 18 ++ .../mj/IncoherentTallyException.java | 13 ++ .../mieuxvoter/mj/InvalidTallyException.java | 9 + .../mj/MajorityJudgmentDeliberator.java | 197 ++++++++++++++++++ .../fr/mieuxvoter/mj/MedianDefaultTally.java | 37 ++++ .../fr/mieuxvoter/mj/NormalizedTally.java | 75 +++++++ .../java/fr/mieuxvoter/mj/ProposalResult.java | 34 +++ .../mj/ProposalResultInterface.java | 21 ++ .../java/fr/mieuxvoter/mj/ProposalTally.java | 92 ++++++++ .../mieuxvoter/mj/ProposalTallyAnalysis.java | 179 ++++++++++++++++ .../mieuxvoter/mj/ProposalTallyInterface.java | 29 +++ src/main/java/fr/mieuxvoter/mj/Result.java | 14 ++ .../fr/mieuxvoter/mj/ResultInterface.java | 12 ++ .../fr/mieuxvoter/mj/StaticDefaultTally.java | 53 +++++ src/main/java/fr/mieuxvoter/mj/Tally.java | 61 ++++++ .../java/fr/mieuxvoter/mj/TallyInterface.java | 12 ++ .../mj/UnbalancedTallyException.java | 20 ++ 19 files changed, 995 insertions(+) create mode 100644 src/main/java/fr/mieuxvoter/mj/CollectedTally.java create mode 100644 src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java create mode 100644 src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java create mode 100644 src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java create mode 100644 src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java create mode 100644 src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java create mode 100644 src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java create mode 100644 src/main/java/fr/mieuxvoter/mj/NormalizedTally.java create mode 100644 src/main/java/fr/mieuxvoter/mj/ProposalResult.java create mode 100644 src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java create mode 100644 src/main/java/fr/mieuxvoter/mj/ProposalTally.java create mode 100644 src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java create mode 100644 src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java create mode 100644 src/main/java/fr/mieuxvoter/mj/Result.java create mode 100644 src/main/java/fr/mieuxvoter/mj/ResultInterface.java create mode 100644 src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java create mode 100644 src/main/java/fr/mieuxvoter/mj/Tally.java create mode 100644 src/main/java/fr/mieuxvoter/mj/TallyInterface.java create mode 100644 src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java diff --git a/src/main/java/fr/mieuxvoter/mj/CollectedTally.java b/src/main/java/fr/mieuxvoter/mj/CollectedTally.java new file mode 100644 index 0000000..2af1cd7 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/CollectedTally.java @@ -0,0 +1,72 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +public class CollectedTally implements TallyInterface { + + Integer amountOfProposals = 0; + Integer amountOfGrades = 0; + + ProposalTally[] proposalsTallies; + + public CollectedTally(Integer amountOfProposals, Integer amountOfGrades) { + setAmountOfProposals(amountOfProposals); + setAmountOfGrades(amountOfGrades); + + proposalsTallies = new ProposalTally[amountOfProposals]; + for (int i = 0; i < amountOfProposals; i++) { + ProposalTally proposalTally = new ProposalTally(); + Integer[] tally = new Integer[amountOfGrades]; + for (int j = 0; j < amountOfGrades; j++) { + tally[j] = 0; + } + proposalTally.setTally(tally); + proposalsTallies[i] = proposalTally; + } + } + + @Override + public ProposalTallyInterface[] getProposalsTallies() { + return proposalsTallies; + } + + @Override + public BigInteger getAmountOfJudges() { + return guessAmountOfJudges(); + } + + @Override + public Integer getAmountOfProposals() { + return this.amountOfProposals; + } + + public void setAmountOfProposals(Integer amountOfProposals) { + this.amountOfProposals = amountOfProposals; + } + + public Integer getAmountOfGrades() { + return amountOfGrades; + } + + public void setAmountOfGrades(Integer amountOfGrades) { + this.amountOfGrades = amountOfGrades; + } + + protected BigInteger guessAmountOfJudges() { + BigInteger amountOfJudges = BigInteger.ZERO; + for (ProposalTallyInterface proposalTally : getProposalsTallies()) { + amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges); + } + return amountOfJudges; + } + + public void collect(Integer proposal, Integer grade) { + assert (0 <= proposal); + assert (amountOfProposals > proposal); + assert (0 <= grade); + assert (amountOfGrades > grade); + + BigInteger[] tally = proposalsTallies[proposal].getTally(); + tally[grade] = tally[grade].add(BigInteger.ONE); + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java new file mode 100644 index 0000000..613de2a --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/DefaultGradeTally.java @@ -0,0 +1,47 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +/** + * Fill the missing judgments into the grade defined by `getDefaultGrade()`. This is an abstract + * class to dry code between static default grade and median default grade. + */ +public abstract class DefaultGradeTally extends Tally implements TallyInterface { + + /** Override this to choose the default grade for a given proposal. */ + protected abstract Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally); + + // /me is confused with why we need constructors in an abstract class? + + public DefaultGradeTally(TallyInterface tally) { + super(tally.getProposalsTallies(), tally.getAmountOfJudges()); + } + + public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) { + super(proposalsTallies, amountOfJudges); + } + + public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) { + super(proposalsTallies, amountOfJudges); + } + + public DefaultGradeTally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) { + super(proposalsTallies, amountOfJudges); + } + + protected void fillWithDefaultGrade() { + int amountOfProposals = getAmountOfProposals(); + for (int i = 0; i < amountOfProposals; i++) { + ProposalTallyInterface proposalTally = getProposalsTallies()[i]; + Integer defaultGrade = getDefaultGradeForProposal(proposalTally); + BigInteger amountOfJudgments = proposalTally.getAmountOfJudgments(); + BigInteger missingAmount = this.amountOfJudges.subtract(amountOfJudgments); + int missingSign = missingAmount.compareTo(BigInteger.ZERO); + assert (0 <= missingSign); // ERROR: More judgments than judges! + if (0 < missingSign) { + BigInteger[] rawTally = proposalTally.getTally(); + rawTally[defaultGrade] = rawTally[defaultGrade].add(missingAmount); + } + } + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java b/src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java new file mode 100644 index 0000000..12120ce --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/DeliberatorInterface.java @@ -0,0 +1,18 @@ +package fr.mieuxvoter.mj; + +/** + * A Deliberator takes in a poll's Tally, which holds the amount of judgments of each grade received + * by each Proposal, and outputs that poll's Result, that is the final rank of each Proposal. + * + *

Ranks start at 1 ("best"), and increment towards "worst". Two proposal may share the same + * rank, in extreme equality cases. + * + *

This is the main API of this library. + * + *

See MajorityJudgmentDeliberator for an implementation. One could implement other deliberators, + * such as: - CentralJudgmentDeliberator - UsualJudgmentDeliberator + */ +public interface DeliberatorInterface { + + public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException; +} diff --git a/src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java b/src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java new file mode 100644 index 0000000..315c8d7 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/IncoherentTallyException.java @@ -0,0 +1,13 @@ +package fr.mieuxvoter.mj; + +/** Raised when the provided tally holds negative values, or infinity. */ +class IncoherentTallyException extends InvalidTallyException { + + private static final long serialVersionUID = 5858986651601202903L; + + @Override + public String getMessage() { + return ("The provided tally holds negative values, or infinity. " + + (null == super.getMessage() ? "" : super.getMessage())); + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java b/src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java new file mode 100644 index 0000000..b055758 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/InvalidTallyException.java @@ -0,0 +1,9 @@ +package fr.mieuxvoter.mj; + +import java.security.InvalidParameterException; + +/** Raised when the provided tally is invalid. */ +class InvalidTallyException extends InvalidParameterException { + + private static final long serialVersionUID = 3033391835216704620L; +} diff --git a/src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java b/src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java new file mode 100644 index 0000000..6123bb5 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/MajorityJudgmentDeliberator.java @@ -0,0 +1,197 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Deliberate using Majority Judgment. + * + *

Sorts Proposals by their median Grade. When two proposals share the same median Grade, give + * reason to the largest group of people that did not give the median Grade. + * + *

This algorithm is score-based, for performance (and possible parallelization). Each Proposal + * gets a score, higher (lexicographically) is "better" (depends of the meaning of the Grades). We + * use Strings instead of Integers or raw Bits for the score. Improve if you feel like it and can + * benchmark things. + * + *

https://en.wikipedia.org/wiki/Majority_judgment + * https://fr.wikipedia.org/wiki/Jugement_majoritaire + * + *

Should this class be `final`? + */ +public final class MajorityJudgmentDeliberator implements DeliberatorInterface { + + protected boolean favorContestation = true; + protected boolean numerizeScore = false; + + public MajorityJudgmentDeliberator() {} + + public MajorityJudgmentDeliberator(boolean favorContestation) { + this.favorContestation = favorContestation; + } + + public MajorityJudgmentDeliberator(boolean favorContestation, boolean numerizeScore) { + this.favorContestation = favorContestation; + this.numerizeScore = numerizeScore; + } + + @Override + public ResultInterface deliberate(TallyInterface tally) throws InvalidTallyException { + checkTally(tally); + + ProposalTallyInterface[] tallies = tally.getProposalsTallies(); + BigInteger amountOfJudges = tally.getAmountOfJudges(); + Integer amountOfProposals = tally.getAmountOfProposals(); + + Result result = new Result(); + ProposalResult[] proposalResults = new ProposalResult[amountOfProposals]; + + // I. Compute the scores of each Proposal + for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) { + ProposalTallyInterface proposalTally = tallies[proposalIndex]; + String score = computeScore(proposalTally, amountOfJudges); + ProposalTallyAnalysis analysis = + new ProposalTallyAnalysis(proposalTally, this.favorContestation); + ProposalResult proposalResult = new ProposalResult(); + proposalResult.setScore(score); + proposalResult.setAnalysis(analysis); + // proposalResult.setRank(???); // rank is computed below, AFTER the score pass + proposalResults[proposalIndex] = proposalResult; + } + + // II. Sort Proposals by score (lexicographical inverse) + ProposalResult[] proposalResultsSorted = proposalResults.clone(); + assert (proposalResultsSorted[0].hashCode() + == proposalResults[0].hashCode()); // we need a shallow clone + Arrays.sort( + proposalResultsSorted, + (Comparator) (p0, p1) -> p1.getScore().compareTo(p0.getScore())); + + // III. Attribute a rank to each Proposal + Integer rank = 1; + for (int proposalIndex = 0; proposalIndex < amountOfProposals; proposalIndex++) { + ProposalResult proposalResult = proposalResultsSorted[proposalIndex]; + Integer actualRank = rank; + if (proposalIndex > 0) { + ProposalResult proposalResultBefore = proposalResultsSorted[proposalIndex - 1]; + if (proposalResult.getScore().contentEquals(proposalResultBefore.getScore())) { + actualRank = proposalResultBefore.getRank(); + } + } + proposalResult.setRank(actualRank); + rank += 1; + } + + result.setProposalResults(proposalResults); + return result; + } + + protected void checkTally(TallyInterface tally) throws UnbalancedTallyException { + if (!isTallyCoherent(tally)) { + throw new IncoherentTallyException(); + } + if (!isTallyBalanced(tally)) { + throw new UnbalancedTallyException(); + } + } + + protected boolean isTallyCoherent(TallyInterface tally) { + boolean coherent = true; + for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) { + for (BigInteger gradeTally : proposalTally.getTally()) { + if (-1 == gradeTally.compareTo(BigInteger.ZERO)) { + coherent = false; // negative tallies are not coherent + } + } + } + + return coherent; + } + + protected boolean isTallyBalanced(TallyInterface tally) { + boolean balanced = true; + BigInteger amountOfJudges = BigInteger.ZERO; + boolean firstProposal = true; + for (ProposalTallyInterface proposalTally : tally.getProposalsTallies()) { + if (firstProposal) { + amountOfJudges = proposalTally.getAmountOfJudgments(); + firstProposal = false; + } else { + if (0 != amountOfJudges.compareTo(proposalTally.getAmountOfJudgments())) { + balanced = false; + } + } + } + + return balanced; + } + + /** @see computeScore() below */ + protected String computeScore(ProposalTallyInterface tally, BigInteger amountOfJudges) { + return computeScore(tally, amountOfJudges, this.favorContestation, this.numerizeScore); + } + + /** + * A higher score means a better rank. Assumes that grades' tallies are provided from "worst" + * grade to "best" grade. + * + * @param tally Holds the tallies of each Grade for a single Proposal + * @param amountOfJudges + * @param favorContestation Use the lower median, for example + * @param onlyNumbers Do not use separation characters, match `^[0-9]+$` + * @return the score of the proposal + */ + protected String computeScore( + ProposalTallyInterface tally, + BigInteger amountOfJudges, + Boolean favorContestation, + Boolean onlyNumbers) { + ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(); + int amountOfGrades = tally.getTally().length; + int digitsForGrade = countDigits(amountOfGrades); + int digitsForGroup = countDigits(amountOfJudges) + 1; + + ProposalTallyInterface currentTally = tally.duplicate(); + + String score = ""; + for (int i = 0; i < amountOfGrades; i++) { + + analysis.reanalyze(currentTally, favorContestation); + + if (0 < i && !onlyNumbers) { + score += "/"; + } + + score += String.format("%0" + digitsForGrade + "d", analysis.getMedianGrade()); + + if (!onlyNumbers) { + score += "_"; + } + + score += + String.format( + "%0" + digitsForGroup + "d", + // We offset by amountOfJudges to keep a lexicographical order (no + // negatives) + // amountOfJudges + secondMedianGroupSize * secondMedianGroupSign + amountOfJudges.add( + analysis.getSecondMedianGroupSize() + .multiply( + BigInteger.valueOf( + analysis.getSecondMedianGroupSign())))); + + currentTally.moveJudgments(analysis.getMedianGrade(), analysis.getSecondMedianGrade()); + } + + return score; + } + + protected int countDigits(int number) { + return ("" + number).length(); + } + + protected int countDigits(BigInteger number) { + return ("" + number).length(); + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java new file mode 100644 index 0000000..1b6c47a --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/MedianDefaultTally.java @@ -0,0 +1,37 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +/** + * Fill the missing judgments into the median grade of each proposal. Useful when the proposals have + * not received the exact same amount of votes and the median grade is considered a sane default. + */ +public class MedianDefaultTally extends DefaultGradeTally implements TallyInterface { + + public MedianDefaultTally(TallyInterface tally) { + super(tally.getProposalsTallies(), tally.getAmountOfJudges()); + fillWithDefaultGrade(); + } + + public MedianDefaultTally( + ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) { + super(proposalsTallies, amountOfJudges); + fillWithDefaultGrade(); + } + + public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) { + super(proposalsTallies, amountOfJudges); + fillWithDefaultGrade(); + } + + public MedianDefaultTally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) { + super(proposalsTallies, amountOfJudges); + fillWithDefaultGrade(); + } + + @Override + protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) { + ProposalTallyAnalysis analysis = new ProposalTallyAnalysis(proposalTally); + return analysis.getMedianGrade(); + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/NormalizedTally.java b/src/main/java/fr/mieuxvoter/mj/NormalizedTally.java new file mode 100644 index 0000000..bab5224 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/NormalizedTally.java @@ -0,0 +1,75 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; +import java.security.InvalidParameterException; + +/** + * The deliberator expects the proposals' tallies to hold the same amount of judgments. This + * NormalizedTally accepts tallies with disparate amounts of judgments per proposal, and normalizes + * them to their least common multiple, which amounts to using percentages, except we don't use + * floating-point arithmetic. + * + *

This is useful when there are too many proposals for judges to be expected to judge them all, + * and all the proposals received reasonably similar amounts of judgments. + */ +public class NormalizedTally extends Tally implements TallyInterface { + + public NormalizedTally(ProposalTallyInterface[] proposalsTallies) { + super(proposalsTallies); + initializeFromProposalsTallies(proposalsTallies); + } + + public NormalizedTally(TallyInterface tally) { + super(tally.getProposalsTallies()); + initializeFromProposalsTallies(tally.getProposalsTallies()); + } + + protected void initializeFromProposalsTallies(ProposalTallyInterface[] proposalsTallies) { + Integer amountOfProposals = getAmountOfProposals(); + + // Compute the Least Common Multiple + BigInteger amountOfJudges = BigInteger.ONE; + for (ProposalTallyInterface proposalTally : proposalsTallies) { + amountOfJudges = lcm(amountOfJudges, proposalTally.getAmountOfJudgments()); + } + + if (0 == amountOfJudges.compareTo(BigInteger.ZERO)) { + throw new InvalidParameterException( + "Cannot normalize: one or more proposals have no judgments."); + } + + // Normalize proposals to the LCM + ProposalTally[] normalizedTallies = new ProposalTally[amountOfProposals]; + for (int i = 0; i < amountOfProposals; i++) { + ProposalTallyInterface proposalTally = proposalsTallies[i]; + ProposalTally normalizedTally = new ProposalTally(proposalTally); + BigInteger factor = amountOfJudges.divide(proposalTally.getAmountOfJudgments()); + Integer amountOfGrades = proposalTally.getTally().length; + BigInteger[] gradesTallies = normalizedTally.getTally(); + for (int j = 0; j < amountOfGrades; j++) { + gradesTallies[j] = gradesTallies[j].multiply(factor); + } + normalizedTallies[i] = normalizedTally; + } + + setProposalsTallies(normalizedTallies); + setAmountOfJudges(amountOfJudges); + } + + /** + * Least Common Multiple + * + *

http://en.wikipedia.org/wiki/Least_common_multiple + * + *

lcm( 6, 9 ) = 18 lcm( 4, 9 ) = 36 lcm( 0, 9 ) = 0 lcm( 0, 0 ) = 0 + * + * @author www.java2s.com + * @param a first integer + * @param b second integer + * @return least common multiple of a and b + */ + public static BigInteger lcm(BigInteger a, BigInteger b) { + if (a.signum() == 0 || b.signum() == 0) return BigInteger.ZERO; + return a.divide(a.gcd(b)).multiply(b).abs(); + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalResult.java b/src/main/java/fr/mieuxvoter/mj/ProposalResult.java new file mode 100644 index 0000000..8e04165 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/ProposalResult.java @@ -0,0 +1,34 @@ +package fr.mieuxvoter.mj; + +public class ProposalResult implements ProposalResultInterface { + + protected Integer rank; + + protected String score; + + protected ProposalTallyAnalysis analysis; + + public Integer getRank() { + return rank; + } + + public void setRank(Integer rank) { + this.rank = rank; + } + + public String getScore() { + return score; + } + + public void setScore(String score) { + this.score = score; + } + + public ProposalTallyAnalysis getAnalysis() { + return analysis; + } + + public void setAnalysis(ProposalTallyAnalysis analysis) { + this.analysis = analysis; + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java b/src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java new file mode 100644 index 0000000..d956a42 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/ProposalResultInterface.java @@ -0,0 +1,21 @@ +package fr.mieuxvoter.mj; + +public interface ProposalResultInterface { + + /** + * Rank starts at 1 ("best" proposal), and goes upwards. Multiple Proposals may receive the same + * rank, in the extreme case where they received the exact same judgments, or the same judgment + * repartition in normalized tallies. + */ + public Integer getRank(); + + /** + * This score was used to compute the rank. It is made of integer characters, with zeroes for + * padding. Inverse lexicographical order: "higher" is "better". You're probably never going to + * need this, but it's here anyway. + */ + public String getScore(); + + /** Get more data about the proposal tally, such as the median grade. */ + public ProposalTallyAnalysis getAnalysis(); +} diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalTally.java b/src/main/java/fr/mieuxvoter/mj/ProposalTally.java new file mode 100644 index 0000000..1e1ee45 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/ProposalTally.java @@ -0,0 +1,92 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; +import java.util.Arrays; + +public class ProposalTally implements ProposalTallyInterface { + + /** + * Amounts of judgments received per grade, from "worst" grade to "best" grade. Those are + * BigIntegers because of our LCM-based normalization shenanigans. + */ + protected BigInteger[] tally; + + public ProposalTally() {} + + public ProposalTally(String[] tally) { + setTally(tally); + } + + public ProposalTally(Integer[] tally) { + setTally(tally); + } + + public ProposalTally(Long[] tally) { + setTally(tally); + } + + public ProposalTally(BigInteger[] tally) { + setTally(tally); + } + + public ProposalTally(ProposalTallyInterface proposalTally) { + setTally(Arrays.copyOf(proposalTally.getTally(), proposalTally.getTally().length)); + } + + public void setTally(String[] tally) { + int tallyLength = tally.length; + BigInteger[] bigTally = new BigInteger[tallyLength]; + for (int i = 0; i < tallyLength; i++) { + bigTally[i] = new BigInteger(tally[i]); + } + setTally(bigTally); + } + + public void setTally(Integer[] tally) { + int tallyLength = tally.length; + BigInteger[] bigTally = new BigInteger[tallyLength]; + for (int i = 0; i < tallyLength; i++) { + bigTally[i] = BigInteger.valueOf(tally[i]); + } + setTally(bigTally); + } + + public void setTally(Long[] tally) { + int tallyLength = tally.length; + BigInteger[] bigTally = new BigInteger[tallyLength]; + for (int i = 0; i < tallyLength; i++) { + bigTally[i] = BigInteger.valueOf(tally[i]); + } + setTally(bigTally); + } + + public void setTally(BigInteger[] tally) { + this.tally = tally; + } + + @Override + public BigInteger[] getTally() { + return this.tally; + } + + @Override + public ProposalTallyInterface duplicate() { + return new ProposalTally(Arrays.copyOf(this.tally, this.tally.length)); + } + + @Override + public void moveJudgments(Integer fromGrade, Integer intoGrade) { + this.tally[intoGrade] = this.tally[intoGrade].add(this.tally[fromGrade]); + this.tally[fromGrade] = BigInteger.ZERO; + } + + @Override + public BigInteger getAmountOfJudgments() { + BigInteger sum = BigInteger.ZERO; + int tallyLength = this.tally.length; + for (int i = 0; i < tallyLength; i++) { + sum = sum.add(this.tally[i]); + } + return sum; + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java b/src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java new file mode 100644 index 0000000..84da69d --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/ProposalTallyAnalysis.java @@ -0,0 +1,179 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +/** + * Collect useful data on a proposal tally. Does NOT compute the rank, but provides all we need. + * + *

This uses BigInteger because in a normalization scenario we use the smallest common multiple + * of the amounts of judges of proposals. It makes the code harder to read and understand, but it + * allows us to bypass the floating-point nightmare of the normalization of merit profiles, which is + * one way to handle default grades on some polls. + */ +public class ProposalTallyAnalysis { + + protected ProposalTallyInterface tally; + + protected BigInteger totalSize = BigInteger.ZERO; // amount of judges + + protected Integer medianGrade = 0; + + protected BigInteger medianGroupSize = BigInteger.ZERO; // amount of judges in the median group + + protected Integer contestationGrade = 0; // "best" grade of the contestation group + + protected BigInteger contestationGroupSize = BigInteger.ZERO; // of lower grades than median + + protected Integer adhesionGrade = 0; // "worst" grade of the adhesion group + + protected BigInteger adhesionGroupSize = BigInteger.ZERO; // of higher grades than median + + protected Integer secondMedianGrade = 0; // grade of the biggest group out of the median + + protected BigInteger secondMedianGroupSize = BigInteger.ZERO; // either contestation or adhesion + + protected Integer secondMedianGroupSign = + 0; // -1 for contestation, +1 for adhesion, 0 for empty group size + + public ProposalTallyAnalysis() {} + + public ProposalTallyAnalysis(ProposalTallyInterface tally) { + reanalyze(tally); + } + + public ProposalTallyAnalysis(ProposalTallyInterface tally, Boolean favorContestation) { + reanalyze(tally, favorContestation); + } + + public void reanalyze(ProposalTallyInterface tally) { + reanalyze(tally, true); + } + + public void reanalyze(ProposalTallyInterface tally, Boolean favorContestation) { + this.tally = tally; + this.totalSize = BigInteger.ZERO; + this.medianGrade = 0; + this.medianGroupSize = BigInteger.ZERO; + this.contestationGrade = 0; + this.contestationGroupSize = BigInteger.ZERO; + this.adhesionGrade = 0; + this.adhesionGroupSize = BigInteger.ZERO; + this.secondMedianGrade = 0; + this.secondMedianGroupSize = BigInteger.ZERO; + this.secondMedianGroupSign = 0; + + BigInteger[] gradesTallies = this.tally.getTally(); + int amountOfGrades = gradesTallies.length; + + for (int grade = 0; grade < amountOfGrades; grade++) { + BigInteger gradeTally = gradesTallies[grade]; + // assert(0 <= gradeTally); // Negative tallies are not allowed. + this.totalSize = this.totalSize.add(gradeTally); + } + + Integer medianOffset = 1; + if (!favorContestation) { + medianOffset = 2; + } + BigInteger medianCursor = + this.totalSize.add(BigInteger.valueOf(medianOffset)).divide(BigInteger.valueOf(2)); + // Long medianCursor = (long) Math.floor((this.totalSize + medianOffset) / 2.0); + + BigInteger tallyBeforeCursor = BigInteger.ZERO; + BigInteger tallyCursor = BigInteger.ZERO; + Boolean foundMedian = false; + Integer contestationGrade = 0; + Integer adhesionGrade = 0; + for (int grade = 0; grade < amountOfGrades; grade++) { + BigInteger gradeTally = gradesTallies[grade]; + tallyBeforeCursor = tallyCursor; + tallyCursor = tallyCursor.add(gradeTally); + + if (!foundMedian) { + if (-1 < tallyCursor.compareTo(medianCursor)) { // tallyCursor >= medianCursor + foundMedian = true; + this.medianGrade = grade; + this.contestationGroupSize = tallyBeforeCursor; + this.medianGroupSize = gradeTally; + this.adhesionGroupSize = + this.totalSize + .subtract(this.contestationGroupSize) + .subtract(this.medianGroupSize); + } else { + if (1 == gradeTally.compareTo(BigInteger.ZERO)) { // 0 < gradeTally + contestationGrade = grade; + } + } + } else { + if (1 == gradeTally.compareTo(BigInteger.ZERO) && 0 == adhesionGrade) { + adhesionGrade = grade; + } + } + } + + this.contestationGrade = contestationGrade; + this.adhesionGrade = adhesionGrade; + this.secondMedianGroupSize = this.contestationGroupSize.max(this.adhesionGroupSize); + this.secondMedianGroupSign = 0; + // if (this.contestationGroupSize < this.adhesionGroupSize) { + if (1 == this.adhesionGroupSize.compareTo(this.contestationGroupSize)) { + this.secondMedianGrade = this.adhesionGrade; + this.secondMedianGroupSign = 1; + // } else if (this.contestationGroupSize > this.adhesionGroupSize) { + } else if (1 == this.contestationGroupSize.compareTo(this.adhesionGroupSize)) { + this.secondMedianGrade = this.contestationGrade; + this.secondMedianGroupSign = -1; + } else { + if (favorContestation) { + this.secondMedianGrade = this.contestationGrade; + this.secondMedianGroupSign = -1; + } else { + this.secondMedianGrade = this.adhesionGrade; + this.secondMedianGroupSign = 1; + } + } + if (0 == this.secondMedianGroupSize.compareTo(BigInteger.ZERO)) { + this.secondMedianGroupSign = 0; + } + } + + public BigInteger getTotalSize() { + return totalSize; + } + + public Integer getMedianGrade() { + return medianGrade; + } + + public BigInteger getMedianGroupSize() { + return medianGroupSize; + } + + public Integer getContestationGrade() { + return contestationGrade; + } + + public BigInteger getContestationGroupSize() { + return contestationGroupSize; + } + + public Integer getAdhesionGrade() { + return adhesionGrade; + } + + public BigInteger getAdhesionGroupSize() { + return adhesionGroupSize; + } + + public Integer getSecondMedianGrade() { + return secondMedianGrade; + } + + public BigInteger getSecondMedianGroupSize() { + return secondMedianGroupSize; + } + + public Integer getSecondMedianGroupSign() { + return secondMedianGroupSign; + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java b/src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java new file mode 100644 index 0000000..e0f23b4 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/ProposalTallyInterface.java @@ -0,0 +1,29 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +/** + * Also known as the merit profile of a proposal (aka. candidate), this holds the amounts of + * judgments received per grade. + */ +public interface ProposalTallyInterface { + + /** + * The tallies of each Grade, that is the amount of judgments received for each Grade by the + * Proposal, from "worst" ("most conservative") Grade to "best" Grade. + */ + public BigInteger[] getTally(); + + /** + * Should be the sum of getTally() + * + * @return The total amount of judgments received by this proposal. + */ + public BigInteger getAmountOfJudgments(); + + /** Homemade factory to skip the clone() shenanigans. Used by the score calculus. */ + public ProposalTallyInterface duplicate(); + + /** Move judgments that were fromGrade into intoGrade. Used by the score calculus. */ + public void moveJudgments(Integer fromGrade, Integer intoGrade); +} diff --git a/src/main/java/fr/mieuxvoter/mj/Result.java b/src/main/java/fr/mieuxvoter/mj/Result.java new file mode 100644 index 0000000..7acca07 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/Result.java @@ -0,0 +1,14 @@ +package fr.mieuxvoter.mj; + +public class Result implements ResultInterface { + + protected ProposalResultInterface[] proposalResults; + + public ProposalResultInterface[] getProposalResults() { + return proposalResults; + } + + public void setProposalResults(ProposalResultInterface[] proposalResults) { + this.proposalResults = proposalResults; + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/ResultInterface.java b/src/main/java/fr/mieuxvoter/mj/ResultInterface.java new file mode 100644 index 0000000..06e163a --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/ResultInterface.java @@ -0,0 +1,12 @@ +package fr.mieuxvoter.mj; + +public interface ResultInterface { + + /** + * ProposalResults are not ordered by rank, they are in the order the proposals' tallies were + * submitted. + * + * @return an array of `ProposalResult`, in the order the `ProposalTally`s were submitted. + */ + public ProposalResultInterface[] getProposalResults(); +} diff --git a/src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java b/src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java new file mode 100644 index 0000000..80b6a86 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/StaticDefaultTally.java @@ -0,0 +1,53 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +public class StaticDefaultTally extends DefaultGradeTally implements TallyInterface { + + /** + * Grades are represented as numbers, as indices in a list. Grades start from 0 ("worst" grade, + * most conservative) and go upwards. Values out of the range of grades defined in the tally + * will yield errors. + * + *

Example: + * + *

0 == REJECT 1 == PASSABLE 2 == GOOD 3 == EXCELLENT + */ + protected Integer defaultGrade = 0; + + public StaticDefaultTally(TallyInterface tally, Integer defaultGrade) { + super(tally.getProposalsTallies(), tally.getAmountOfJudges()); + this.defaultGrade = defaultGrade; + fillWithDefaultGrade(); + } + + public StaticDefaultTally( + ProposalTallyInterface[] proposalsTallies, + BigInteger amountOfJudges, + Integer defaultGrade) { + super(proposalsTallies, amountOfJudges); + this.defaultGrade = defaultGrade; + fillWithDefaultGrade(); + } + + public StaticDefaultTally( + ProposalTallyInterface[] proposalsTallies, Long amountOfJudges, Integer defaultGrade) { + super(proposalsTallies, amountOfJudges); + this.defaultGrade = defaultGrade; + fillWithDefaultGrade(); + } + + public StaticDefaultTally( + ProposalTallyInterface[] proposalsTallies, + Integer amountOfJudges, + Integer defaultGrade) { + super(proposalsTallies, amountOfJudges); + this.defaultGrade = defaultGrade; + fillWithDefaultGrade(); + } + + @Override + protected Integer getDefaultGradeForProposal(ProposalTallyInterface proposalTally) { + return this.defaultGrade; + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/Tally.java b/src/main/java/fr/mieuxvoter/mj/Tally.java new file mode 100644 index 0000000..0331a0d --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/Tally.java @@ -0,0 +1,61 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +/** + * A Basic implementation of a TallyInterface that reads from an array of ProposalTallyInterface. + */ +public class Tally implements TallyInterface { + + protected ProposalTallyInterface[] proposalsTallies; + + protected BigInteger amountOfJudges = BigInteger.ZERO; + + public Tally(ProposalTallyInterface[] proposalsTallies) { + setProposalsTallies(proposalsTallies); + guessAmountOfJudges(); + } + + public Tally(ProposalTallyInterface[] proposalsTallies, BigInteger amountOfJudges) { + setProposalsTallies(proposalsTallies); + setAmountOfJudges(amountOfJudges); + } + + public Tally(ProposalTallyInterface[] proposalsTallies, Long amountOfJudges) { + setProposalsTallies(proposalsTallies); + setAmountOfJudges(BigInteger.valueOf(amountOfJudges)); + } + + public Tally(ProposalTallyInterface[] proposalsTallies, Integer amountOfJudges) { + setProposalsTallies(proposalsTallies); + setAmountOfJudges(BigInteger.valueOf(amountOfJudges)); + } + + public ProposalTallyInterface[] getProposalsTallies() { + return proposalsTallies; + } + + public void setProposalsTallies(ProposalTallyInterface[] proposalsTallies) { + this.proposalsTallies = proposalsTallies; + } + + public Integer getAmountOfProposals() { + return proposalsTallies.length; + } + + public BigInteger getAmountOfJudges() { + return amountOfJudges; + } + + public void setAmountOfJudges(BigInteger amountOfJudges) { + this.amountOfJudges = amountOfJudges; + } + + protected void guessAmountOfJudges() { + BigInteger amountOfJudges = BigInteger.ZERO; + for (ProposalTallyInterface proposalTally : getProposalsTallies()) { + amountOfJudges = proposalTally.getAmountOfJudgments().max(amountOfJudges); + } + setAmountOfJudges(amountOfJudges); + } +} diff --git a/src/main/java/fr/mieuxvoter/mj/TallyInterface.java b/src/main/java/fr/mieuxvoter/mj/TallyInterface.java new file mode 100644 index 0000000..33a41f1 --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/TallyInterface.java @@ -0,0 +1,12 @@ +package fr.mieuxvoter.mj; + +import java.math.BigInteger; + +public interface TallyInterface { + + public ProposalTallyInterface[] getProposalsTallies(); + + public BigInteger getAmountOfJudges(); + + public Integer getAmountOfProposals(); +} diff --git a/src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java b/src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java new file mode 100644 index 0000000..abd590d --- /dev/null +++ b/src/main/java/fr/mieuxvoter/mj/UnbalancedTallyException.java @@ -0,0 +1,20 @@ +package fr.mieuxvoter.mj; + +/** + * Raised when the provided tally does not hold the same amount of judgments for each proposal, and + * normalization is required. + */ +class UnbalancedTallyException extends InvalidTallyException { + + private static final long serialVersionUID = 5041093000505081735L; + + @Override + public String getMessage() { + return ("The provided tally is unbalanced, as some proposals received more judgments than" + + " others. \n" + + "You need to set a strategy for balancing tallies. To that effect, \n" + + "you may use StaticDefaultTally, MedianDefaultTally, or NormalizedTally" + + " instead of Tally. \n" + + (null == super.getMessage() ? "" : super.getMessage())); + } +} From d20323553000abafbb37f4eda1c1c2d5c50edd09 Mon Sep 17 00:00:00 2001 From: Arimihanta Date: Mon, 12 Jul 2021 19:34:46 +0300 Subject: [PATCH 3/4] Groupe pour l'envoie d'email --- src/main/java/fr/lirmm/aren/model/User.java | 45 +++++++++ .../java/fr/lirmm/aren/model/vm/VMTeam.java | 81 ++++++++++++++++ .../java/fr/lirmm/aren/model/vm/VMTheme.java | 19 ++++ .../lirmm/aren/service/vm/VMTeamService.java | 94 +++++++++++++++++++ .../lirmm/aren/ws/rest/VMTeamRESTFacade.java | 90 ++++++++++++++++++ 5 files changed, 329 insertions(+) create mode 100644 src/main/java/fr/lirmm/aren/model/vm/VMTeam.java create mode 100644 src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java create mode 100644 src/main/java/fr/lirmm/aren/ws/rest/VMTeamRESTFacade.java diff --git a/src/main/java/fr/lirmm/aren/model/User.java b/src/main/java/fr/lirmm/aren/model/User.java index 9a7c960..e0312a7 100644 --- a/src/main/java/fr/lirmm/aren/model/User.java +++ b/src/main/java/fr/lirmm/aren/model/User.java @@ -25,6 +25,8 @@ import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; import javax.persistence.JoinTable; + +import fr.lirmm.aren.model.vm.VMTeam; import org.hibernate.annotations.Filter; import org.hibernate.annotations.Filters; import org.hibernate.annotations.SortNatural; @@ -135,6 +137,15 @@ public class User extends AbstractEntEntity implements Serializable { @SortNatural private SortedSet teams = new TreeSet<>(); + @JoinTable(name = "vm_teams_users", + joinColumns = { + @JoinColumn(name = "user_id", referencedColumnName = "id")}, + inverseJoinColumns = { + @JoinColumn(name = "team_id", referencedColumnName = "id")}) + @ManyToMany + @SortNatural + private SortedSet vmTeams = new TreeSet<>(); + @OneToMany(mappedBy = "owner") @SortNatural private SortedSet notifications = new TreeSet<>(); @@ -372,6 +383,40 @@ public class User extends AbstractEntEntity implements Serializable { team.getUsers().remove(this); } + /** + * + * @return + */ + public SortedSet getVmTeams() { + return vmTeams; + } + + /** + * + * @param vmTeams + */ + public void setVmTeams(SortedSet vmTeams) { + this.vmTeams = vmTeams; + } + + /** + * + * @param team + */ + public void addMember(VMTeam team) { + vmTeams.add(team); + team.getMembers().add(this); + } + + /** + * + * @param team + */ + public void removeMember(VMTeam team) { + vmTeams.remove(team); + team.getMembers().remove(this); + } + /** * * @return diff --git a/src/main/java/fr/lirmm/aren/model/vm/VMTeam.java b/src/main/java/fr/lirmm/aren/model/vm/VMTeam.java new file mode 100644 index 0000000..4a58ace --- /dev/null +++ b/src/main/java/fr/lirmm/aren/model/vm/VMTeam.java @@ -0,0 +1,81 @@ +package fr.lirmm.aren.model.vm; + +import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import fr.lirmm.aren.model.AbstractEntity; +import fr.lirmm.aren.model.User; +import org.hibernate.annotations.SortNatural; +import org.hibernate.annotations.Where; + +import javax.persistence.*; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.SortedSet; +import java.util.TreeSet; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 11/07/2021 + * @project aren-1 + */ +@Entity +@Table(name = "vm_teams") +@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = VMTheme.class) +public class VMTeam extends AbstractEntity implements Serializable { + @Size(max = 255) + @Column(name = "name") + private String name; + + @ManyToMany(mappedBy = "vmTeams") + @SortNatural + private SortedSet members = new TreeSet<>(); + + /** + * + * @return + */ + public String getName() { + return name; + } + + /** + * + * @param name + */ + public void setName(String name) { + this.name = name; + } + + /** + * + * @return + */ + public SortedSet getMembers() { + return members; + } + + /** + * + * @param members + */ + public void setMembers(SortedSet members) { + this.members = members; + } + + /** + * + * @param user + */ + public void addUser(User user) { + this.members.add(user); + user.getVmTeams().add(this); + } + + /** + * + * @param user + */ + public void removeUser(User user) { + this.members.remove(user); + user.getVmTeams().remove(this); + } +} \ No newline at end of file diff --git a/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java b/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java index 7befdfa..bf0dfc6 100644 --- a/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java +++ b/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java @@ -29,6 +29,9 @@ public class VMTheme extends AbstractEntity implements Serializable { @ManyToOne private User author; + @JoinColumn(name = "team", referencedColumnName = "id") + @ManyToOne + private VMTeam team; @Size(max = 255) @Column(name = "title") @@ -64,6 +67,22 @@ public class VMTheme extends AbstractEntity implements Serializable { this.author = author; } + /** + * + * @return + */ + public VMTeam getTeam() { + return team; + } + + /** + * + * @param team + */ + public void setTeam(VMTeam team) { + this.team = team; + } + /** * * @return diff --git a/src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java b/src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java new file mode 100644 index 0000000..fe6f6b8 --- /dev/null +++ b/src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java @@ -0,0 +1,94 @@ +package fr.lirmm.aren.service.vm; + +import fr.lirmm.aren.model.Debate; +import fr.lirmm.aren.model.Team; +import fr.lirmm.aren.model.vm.VMTeam; +import fr.lirmm.aren.service.AbstractService; + +import javax.enterprise.context.ApplicationScoped; +import javax.persistence.TypedQuery; +import javax.ws.rs.NotFoundException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 11/07/2021 + * @project aren-1 + */ +@ApplicationScoped +public class VMTeamService extends AbstractService { + /** + * + */ + public VMTeamService(){ + super(VMTeam.class) ; + } + + /** + * + * @param teamId + * @return + */ + private TypedQuery generateQuery(Long teamId){ + TypedQuery query = getEntityManager().createQuery("SELECT vmt " + +"FROM VMTeam vmt " + +"LEFT JOIN FETCH vmt.members m " + + (teamId != null + ? "WHERE vmt.id = :teamId " + : "WHERE vmt.id IS NOT NULL ") + , VMTeam.class + ) ; + if (teamId != null) { + query.setParameter("teamId", teamId); + } + return query ; + } + + /** + * + * @return + */ + public Set findAll() { + return new HashSet(generateQuery(null).getResultList()); + } + + /** + * + * @param team + */ + @Override + public void edit(VMTeam team) { + super.edit(team); + } + + @Override + public VMTeam insert(VMTeam team) { + return super.insert(team) ; + } + + /** + * + * @param teamId + * @return + */ + public VMTeam find(Long teamId) { + List teams = generateQuery(teamId).getResultList(); + if (teams.isEmpty()) { + throw new NotFoundException(); + } + return teams.get(0); + } + + public void updateExternaleTables(VMTeam team) { + super.transactionBegin(); + team.getMembers().forEach(member->{ + System.out.println(team.getId()+" --- "+member.getId()); + getEntityManager().createNativeQuery("INSERT INTO vm_teams_users(vmteam_id,members_id) VALUES(?,?) ") + .setParameter(1, team.getId()) + .setParameter(2, member.getId()) + .executeUpdate(); + super.commit(); + }); + } +} diff --git a/src/main/java/fr/lirmm/aren/ws/rest/VMTeamRESTFacade.java b/src/main/java/fr/lirmm/aren/ws/rest/VMTeamRESTFacade.java new file mode 100644 index 0000000..04af0ad --- /dev/null +++ b/src/main/java/fr/lirmm/aren/ws/rest/VMTeamRESTFacade.java @@ -0,0 +1,90 @@ +package fr.lirmm.aren.ws.rest; + +import fr.lirmm.aren.model.Team; +import fr.lirmm.aren.model.User; +import fr.lirmm.aren.model.vm.VMTeam; +import fr.lirmm.aren.service.UserService; +import fr.lirmm.aren.service.vm.VMTeamService; + +import javax.annotation.security.PermitAll; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import java.util.Set; +import java.util.SortedSet; + +/** + * @author ANDRIAMBOLAHARIMIHANTA Havana on 11/07/2021 + * @project aren-1 + */ +@RequestScoped +@Path("vm/teams") +public class VMTeamRESTFacade extends AbstractRESTFacade{ + @Inject + VMTeamService teamService ; + + @Inject + UserService userService ; + + /** + * + * @return + */ + @Override + protected VMTeamService getService(){ + return teamService ; + } + + /** + * + * @param team + * @return + */ + @Override + @PermitAll + public VMTeam create(VMTeam team){ + SortedSet members=team.getMembers() ; + VMTeam teamRes= teamService.insert(team) ; + members.forEach(member->{ + teamRes.addUser(userService.getReference(member.getId())); + teamService.edit(teamRes); + }); + return teamRes ; + } + + /** + * + * @param id of the Entity to update + * @param team + * @return + */ + @Override + public VMTeam edit(Long id, VMTeam team){ + return super.edit(id,team) ; + } + + /** + * + * @return + */ + @Override + @PermitAll + public Set findAll() { + Set teams= teamService.findAll(); + return teams ; + } + + /** + * + * @param id of the Entity to fetch + * @return + */ + @Override + @PermitAll + public VMTeam find(Long id) { + return teamService.find(id); + } +} From ab1f30e71865e6c6ec7bc61792be19bd201a1c48 Mon Sep 17 00:00:00 2001 From: Arimihanta Date: Mon, 12 Jul 2021 20:16:08 +0300 Subject: [PATCH 4/4] Groupe pour l'envoie d'email pour vote majoritaire --- src/main/java/fr/lirmm/aren/model/vm/VMTheme.java | 7 ++++--- src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java b/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java index bf0dfc6..e01973b 100644 --- a/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java +++ b/src/main/java/fr/lirmm/aren/model/vm/VMTheme.java @@ -3,6 +3,7 @@ package fr.lirmm.aren.model.vm; import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import fr.lirmm.aren.model.AbstractEntity; +import fr.lirmm.aren.model.Team; import fr.lirmm.aren.model.User; import org.hibernate.annotations.Type; @@ -31,7 +32,7 @@ public class VMTheme extends AbstractEntity implements Serializable { @JoinColumn(name = "team", referencedColumnName = "id") @ManyToOne - private VMTeam team; + private Team team; @Size(max = 255) @Column(name = "title") @@ -71,7 +72,7 @@ public class VMTheme extends AbstractEntity implements Serializable { * * @return */ - public VMTeam getTeam() { + public Team getTeam() { return team; } @@ -79,7 +80,7 @@ public class VMTheme extends AbstractEntity implements Serializable { * * @param team */ - public void setTeam(VMTeam team) { + public void setTeam(Team team) { this.team = team; } diff --git a/src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java b/src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java index fe6f6b8..732a132 100644 --- a/src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java +++ b/src/main/java/fr/lirmm/aren/service/vm/VMTeamService.java @@ -33,7 +33,7 @@ public class VMTeamService extends AbstractService { private TypedQuery generateQuery(Long teamId){ TypedQuery query = getEntityManager().createQuery("SELECT vmt " +"FROM VMTeam vmt " - +"LEFT JOIN FETCH vmt.members m " + +"LEFT JOIN vmt.members m " + (teamId != null ? "WHERE vmt.id = :teamId " : "WHERE vmt.id IS NOT NULL ")