Java tutorial
// // Copyright (c) 2011 Linkeos. // // This file is part of Elveos.org. // Elveos.org is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation, either version 3 of the License, or (at your // option) any later version. // // Elveos.org is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // You should have received a copy of the GNU General Public License along // with Elveos.org. If not, see http://www.gnu.org/licenses/. // package com.bloatit.data; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.EnumSet; import java.util.List; import javax.persistence.Basic; import javax.persistence.Cacheable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Enumerated; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import org.hibernate.Query; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.NamedQueries; import org.hibernate.annotations.NamedQuery; import org.hibernate.annotations.OrderBy; import org.hibernate.search.annotations.DateBridge; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Index; import org.hibernate.search.annotations.IndexedEmbedded; import org.hibernate.search.annotations.Resolution; import org.hibernate.search.annotations.Store; import com.bloatit.common.Log; import com.bloatit.data.DaoBug.BugState; import com.bloatit.data.DaoBug.Level; import com.bloatit.data.exceptions.NotEnoughMoneyException; import com.bloatit.data.queries.QueryCollection; import com.bloatit.framework.exceptions.highlevel.BadProgrammerException; import com.bloatit.framework.exceptions.lowlevel.NonOptionalParameterException; import com.bloatit.framework.utils.PageIterable; import com.bloatit.framework.utils.datetime.DateUtils; /** * The Class DaoMilestone. */ @Entity @Cacheable @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //@formatter:off @NamedQueries(value = { @NamedQuery(name = "milestone.getBugs.byNonStateLevel", query = "FROM DaoBug WHERE milestone = :this AND state != :state AND level = :level"), @NamedQuery(name = "milestone.getBugs.byNonStateLevel.size", query = "SELECT count(*) FROM DaoBug WHERE milestone = :this AND state != :state AND level = :level"), @NamedQuery(name = "milestone.getBugs.byState", query = "FROM DaoBug WHERE milestone = :this AND state = :state"), @NamedQuery(name = "milestone.getBugs.getAll", query = "FROM DaoBug WHERE milestone = :this"), @NamedQuery(name = "milestone.getBugs.getAll.size", query = "SELECT count(*) FROM DaoBug WHERE milestone = :this"), @NamedQuery(name = "milestone.getBugs.byState.size", query = "SELECT count(*) FROM DaoBug WHERE milestone = :this AND state != :state"), @NamedQuery(name = "milestone.getBugs.byLevel", query = "FROM DaoBug WHERE milestone = :this AND level = :level"), @NamedQuery(name = "milestone.getBugs.byLevel.size", query = "SELECT count (*) FROM DaoBug WHERE milestone = :this AND level = :level"), @NamedQuery(name = "milestone.getBugs.byStateLevel", query = "FROM DaoBug WHERE milestone = :this AND state = :state AND level = :level"), @NamedQuery(name = "milestone.getBugs.byStateLevel.size", query = "SELECT count(*) FROM DaoBug WHERE milestone = :this AND state = :state AND level = :level"), @NamedQuery(name = "milestone.getAmountPaid", query = "SELECT SUM(co.amount)" + "FROM DaoMilestoneContributionAmount as co " + "WHERE co.milestone = :this"), }) // @formatter:on /** * A DaoMilestone is a part of a DaoOffer. * * @author Thomas Guyard */ public class DaoMilestone extends DaoIdentifiable { /** * The Enum MilestoneState. */ public enum MilestoneState { /** The PENDING state is the default creation state. */ PENDING, /** The DEVELOPING state is the state when a developer is doing its job. */ DEVELOPING, /** * The UAT state is when the milestone has a release but is not in * validated state yet (Bugs ...) */ UAT, /** The VALIDATED is a final state. */ VALIDATED, /** The CANCELED is a final state. */ CANCELED } /** * After this date, the Milestone should be done. */ @Basic(optional = false) @Field(index = Index.UN_TOKENIZED, store = Store.YES) @DateBridge(resolution = Resolution.DAY) @Column(updatable = false) private Date expirationDate; /** The second before validation. */ @Basic(optional = false) @Column(updatable = false) private int secondBeforeValidation; /** The fatal bugs percent. */ @Basic(optional = false) @Column(updatable = false) private int fatalBugsPercent; /** The major bugs percent. */ @Basic(optional = false) @Column(updatable = false) private int majorBugsPercent; /** * The amount represents the money the member want to have to make his * offer. */ @Basic(optional = false) @Column(updatable = false) // @Cache(usage=CacheConcurrencyStrategy.READ_ONLY) private BigDecimal amount; // nullable. /** The level to validate. */ @Enumerated private Level levelToValidate; /** The milestone state. */ @Basic(optional = false) private MilestoneState milestoneState; /** * Remember a description is a title with some content. (Translatable) */ @ManyToOne @Cascade(value = { CascadeType.ALL }) @IndexedEmbedded @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private DaoDescription description; /** The bugs. */ @OneToMany(mappedBy = "milestone") @Cascade(value = { CascadeType.ALL }) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private final List<DaoBug> bugs = new ArrayList<DaoBug>(); /** The releases. */ @OneToMany(mappedBy = "milestone") @Cascade(value = { CascadeType.ALL }) @OrderBy(clause = "id DESC") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private final List<DaoRelease> releases = new ArrayList<DaoRelease>(); /** The contribution amount. */ @OneToMany(mappedBy = "milestone") @Cascade(value = { CascadeType.ALL }) @OrderBy(clause = "id DESC") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private final List<DaoMilestoneContributionAmount> contribtutionAmounts = new ArrayList<DaoMilestoneContributionAmount>(); /** The offer. */ @ManyToOne(optional = false) private DaoOffer offer; /** The invoices */ @SuppressWarnings("unused") @OneToMany(mappedBy = "milestone") @Cascade(value = { CascadeType.ALL }) @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) private final List<DaoContributionInvoice> invoices = new ArrayList<DaoContributionInvoice>(); /** * The lastPaymentDate is the date of the last payment of a contribution * part on this milestone. */ @Basic(optional = true) private Date lastPaymentDate; @SuppressWarnings("unused") @OneToMany(mappedBy = "milestone", cascade = { javax.persistence.CascadeType.ALL }) private List<DaoEvent> event; // ====================================================================== // Construction. // ====================================================================== /** * Create a DaoMilestone. * * @param dateExpire is the date when this offer should be finish. Must be * non null, and in the future. * @param amount is the amount of the offer. Must be non null, and > 0. * @param description the description * @param offer the offer * @param secondBeforeValidation number of seconds to wait until we watch * for bugs and validate the milestone. */ public DaoMilestone(final Date dateExpire, final BigDecimal amount, final DaoDescription description, final DaoOffer offer, final int secondBeforeValidation) { super(); if (dateExpire == null || amount == null || description == null || offer == null) { throw new NonOptionalParameterException(); } if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new BadProgrammerException("Amount must be > 0"); } if (dateExpire.before(new Date())) { throw new BadProgrammerException("Make sure the date is in the future."); } this.expirationDate = (Date) dateExpire.clone(); this.amount = amount; this.description = description; this.offer = offer; this.secondBeforeValidation = secondBeforeValidation; this.levelToValidate = Level.FATAL; this.fatalBugsPercent = 100; this.majorBugsPercent = 0; this.milestoneState = MilestoneState.PENDING; } /** * Set the percent of money the developer will received when all bugs of one * level are closed. This method take parameters for the Fatal and Major * level. The Minor level is calculated from it (see * * @param fatalPercent is the percent of the money the developer will get * when all the {@link Level#FATAL} bugs are closed. It must be * >= 0 and <= 100. * @param majorPercent is the percent of the money the developer will get * when all the {@link Level#MAJOR} bugs are closed. It must be * >= 0 and <= 100. {@link #getMinorBugsPercent()}). */ public void updateMajorFatalPercent(final int fatalPercent, final int majorPercent) { if (fatalPercent < 0 || majorPercent < 0) { throw new BadProgrammerException("The parameters must be percents !"); } if ((fatalPercent + majorPercent) > 100) { throw new BadProgrammerException("The sum of the two percent parameters is > 100 !"); } this.fatalBugsPercent = fatalPercent; this.majorBugsPercent = majorPercent; } /** * Sets the developing. */ public void setDeveloping() { this.milestoneState = MilestoneState.DEVELOPING; } /** * Adds the release. * * @param release the release */ public void addRelease(final DaoRelease release) { this.releases.add(release); if (this.milestoneState == MilestoneState.DEVELOPING) { this.milestoneState = MilestoneState.UAT; } getOffer().milestoneHasARelease(this); } /** * Adds the bug. * * @param bug the bug */ public void addBug(final DaoBug bug) { this.bugs.add(bug); } /** * Tells that the Income state of this milestone is finished, and everything * is OK. The validation can be partial (when some major or minor bugs are * open). The validate method may also validate nothing if some FATAL bugs * are open, or if the validation period is not open. You can change this * behavior using the <code>force</code> parameter. The force parameter * allows to validate the milestone without taking into account these * previous restrictions. * * @param force force the validation of this milestone. Do not take care of * the bugs and the timeOuts. * @return true if all parts of this milestone is validated. */ public boolean validate(final boolean force) { // // Calculate the real percent (= percent of this milestone * percent of // this // level). final int milestonePercent = this.offer.getMilestonePercent(this); final int fatalPercent = (milestonePercent * this.fatalBugsPercent) / 100; final int majorPercent = (milestonePercent * this.majorBugsPercent) / 100; final int minorPercent = milestonePercent - majorPercent - fatalPercent; // // Do the validation // if (this.levelToValidate == Level.FATAL && (force || shouldValidatePart(Level.FATAL))) { this.levelToValidate = Level.MAJOR; validateContributions(fatalPercent); } // if fatalBugPercent == 100, there is nothing left to validate so it is // automatically validated. if (this.levelToValidate == Level.MAJOR && (force || shouldValidatePart(Level.MAJOR) || this.fatalBugsPercent == 100)) { this.levelToValidate = Level.MINOR; validateContributions(majorPercent); } // when minorBugPercent == 0, there is nothing left to validate so it is // automatically validated. if (this.levelToValidate == Level.MINOR && (force || shouldValidatePart(Level.MINOR) || getMinorBugsPercent() == 0)) { this.levelToValidate = null; validateContributions(minorPercent); } if (this.levelToValidate == null) { this.milestoneState = MilestoneState.VALIDATED; this.offer.passToNextMilestone(); return true; } return false; } /** * Validate contributions. * * @param percent the percent */ private void validateContributions(final int percent) { if (percent == 0) { return; } for (final DaoContribution contribution : this.offer.getFeature().getContributions()) { try { if (contribution.getState() == DaoContribution.ContributionState.PENDING) { final BigDecimal amount = contribution.validate(this, percent); contribtutionAmounts .add(DaoMilestoneContributionAmount.updateOrCreate(this, contribution, amount)); lastPaymentDate = DateUtils.now(); } } catch (final NotEnoughMoneyException e) { Log.data().fatal("Cannot validate contribution, not enough money: " + contribution.getId(), e); } } } /** * You can validate a milestone after its release and when the bugs * requirement are done. * * @param level the level * @return true if an admin should validate this Milestone part. False * otherwise. */ public boolean shouldValidatePart(final Level level) { if (validationPeriodFinished() && getNonResolvedBugs(level).size() == 0) { return true; } return false; } /** * Tells if a specified level is validated (and the corresponding amount has * been given to the developer) * * @param level the level * @return true, the <code>level</code> is validated. */ public boolean partIsValidated(final Level level) { return this.levelToValidate == null || !EnumSet.range(this.levelToValidate, Level.MINOR).contains(level); } /** * Validation period finished. * * @return true, if successful */ private boolean validationPeriodFinished() { final Date releasedDate = getReleasedDate(); if (releasedDate == null) { return false; } return new Date(releasedDate.getTime() + ((long) this.secondBeforeValidation) * 1000).before(new Date()); } /** * Cancel milestone. */ public void cancelMilestone() { this.milestoneState = MilestoneState.CANCELED; } // ====================================================================== // Getters. // ====================================================================== /** * Gets the non resolved bugs. * * @param level the level * @return the non resolved bugs */ public PageIterable<DaoBug> getNonResolvedBugs(final Level level) { return new QueryCollection<DaoBug>("milestone.getBugs.byNonStateLevel").setEntity("this", this) .setParameter("level", level).setParameter("state", BugState.RESOLVED); } /** * @return all the bugs linked to the milestone */ public PageIterable<DaoBug> getBugs() { return new QueryCollection<DaoBug>("milestone.getBugs.getAll").setEntity("this", this); } /** * Gets the bugs. * * @param level the level * @return the bugs */ public PageIterable<DaoBug> getBugs(final Level level) { return new QueryCollection<DaoBug>("milestone.getBugs.byLevel").setEntity("this", this) .setParameter("level", level); } /** * Gets the bugs. * * @param state the state * @return the bugs */ public PageIterable<DaoBug> getBugs(final BugState state) { return new QueryCollection<DaoBug>("milestone.getBugs.byState").setEntity("this", this) .setParameter("state", state); } /** * Gets the bugs. * * @param level the level * @param state the state * @return the bugs */ public PageIterable<DaoBug> getBugs(final Level level, final BugState state) { return new QueryCollection<DaoBug>("milestone.getBugs.byStateLevel").setEntity("this", this) .setParameter("level", level).setParameter("state", state); } /** * Gets the second before validation. * * @return the second before validation */ public int getSecondBeforeValidation() { return secondBeforeValidation; } /** * Gets the releases. * * @return the releases */ public PageIterable<DaoRelease> getReleases() { return new MappedUserContentList<DaoRelease>(this.releases); } /** * Gets the after this date, the Milestone should be done. * * @return the after this date, the Milestone should be done */ public Date getExpirationDate() { return (Date) this.expirationDate.clone(); } /** * Gets the milestone state. * * @return the milestone state */ public MilestoneState getMilestoneState() { return this.milestoneState; } /** * Gets the amount represents the money the member want to have to make his * offer. * * @return the amount represents the money the member want to have to make * his offer */ public BigDecimal getAmount() { return this.amount; } public BigDecimal getAmountPaid() { final Query q = SessionManager.getNamedQuery("milestone.getAmountPaid"); q.setEntity("this", this); final BigDecimal uniqueResult = (BigDecimal) q.uniqueResult(); if (uniqueResult == null) { return BigDecimal.ZERO; } return uniqueResult; } /** * Remember a description is a title with some content. * * @return the description */ public DaoDescription getDescription() { return this.description; } /** * Gets the offer. * * @return the offer */ public DaoOffer getOffer() { return this.offer; } /** * Gets the released date. * * @return the releaseDate */ public Date getReleasedDate() { final Query query = SessionManager.createFilter(this.releases, "select max(creationDate)"); return (Date) query.uniqueResult(); } /** * Gets the fatal bugs percent. * * @return the fatalBugsPercent */ public int getFatalBugsPercent() { return this.fatalBugsPercent; } /** * Gets the major bugs percent. * * @return the majorBugsPercent */ public int getMajorBugsPercent() { return this.majorBugsPercent; } /** * Gets the minor bugs percent. * * @return the getMinorBugsPercent (= 100 - (majorBugsPercent + * fatalBugsPercent)). */ public int getMinorBugsPercent() { return 100 - (this.majorBugsPercent + this.fatalBugsPercent); } public PageIterable<DaoMilestoneContributionAmount> getContributionAmounts() { return new MappedList<DaoMilestoneContributionAmount>(contribtutionAmounts); } public Date getLastPaymentDate() { final Date date = lastPaymentDate; if (date == null) { return DateUtils.now(); } else { return date; } } // ====================================================================== // Visitor. // ====================================================================== /* * (non-Javadoc) * @see * com.bloatit.data.DaoIdentifiable#accept(com.bloatit.data.DataClassVisitor * ) */ @Override public <ReturnType> ReturnType accept(final DataClassVisitor<ReturnType> visitor) { return visitor.visit(this); } // ====================================================================== // For hibernate mapping // ====================================================================== /** * Instantiates a new dao milestone. */ protected DaoMilestone() { super(); } // ====================================================================== // equals and hashCode. // ====================================================================== /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this.amount == null) ? 0 : this.amount.hashCode()); result = prime * result + ((this.description == null) ? 0 : this.description.hashCode()); result = prime * result + ((this.expirationDate == null) ? 0 : this.expirationDate.hashCode()); result = prime * result + this.fatalBugsPercent; result = prime * result + this.majorBugsPercent; result = prime * result + ((this.offer == null) ? 0 : this.offer.hashCode()); result = prime * result + this.secondBeforeValidation; return result; } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final DaoMilestone other = (DaoMilestone) obj; if (this.amount == null) { if (other.amount != null) { return false; } } else if (!this.amount.equals(other.amount)) { return false; } if (this.description == null) { if (other.description != null) { return false; } } else if (!this.description.equals(other.description)) { return false; } if (this.expirationDate == null) { if (other.expirationDate != null) { return false; } } else if (!this.expirationDate.equals(other.expirationDate)) { return false; } if (this.fatalBugsPercent != other.fatalBugsPercent) { return false; } if (this.majorBugsPercent != other.majorBugsPercent) { return false; } if (this.offer == null) { if (other.offer != null) { return false; } } else if (!this.offer.equals(other.offer)) { return false; } if (this.secondBeforeValidation != other.secondBeforeValidation) { return false; } return true; } }