Java tutorial
/** * Copyright (C) 2014 Frank Steiler <frank@steilerdev.de> * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package de.steilerdev.myVerein.server.model; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import org.hibernate.validator.constraints.NotBlank; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.mongodb.LazyLoadingException; import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.mapping.DBRef; import java.util.*; import java.util.stream.Collectors; /** * This object is representing an entity within the division's collection of the MongoDB. On top of that the class is providing several useful helper methods. */ public class Division implements Comparable<Division> { @Id private String id; @Indexed @NotBlank @JsonInclude(JsonInclude.Include.NON_NULL) private String name; @JsonInclude(JsonInclude.Include.NON_NULL) private String desc; @JsonInclude(JsonInclude.Include.NON_NULL) @DBRef(lazy = true) private User adminUser; @JsonIgnore @DBRef(lazy = true) private Division parent; @JsonIgnore @DBRef //(lazy = true) private List<Division> ancestors; @JsonIgnore private List<String> memberList; @JsonIgnore @Transient private static Logger logger = LoggerFactory.getLogger(Division.class); public Division() { } public Division(String name, String desc, User adminUser, Division parent, List<Division> ancestors) { this.name = name; this.desc = desc; this.adminUser = adminUser; this.parent = parent; this.ancestors = ancestors; } public Division(String name, String desc, User adminUser, Division parent) { this.name = name; this.desc = desc; this.adminUser = adminUser; this.setParent(parent); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public User getAdminUser() { return adminUser; } public void setAdminUser(User adminUser) { this.adminUser = adminUser; } public Division getParent() { return parent; } public List<String> getMemberList() { return memberList; } public void setMemberList(List<String> memberList) { this.memberList = memberList; } public void addMember(User user) { if (memberList == null) { memberList = new ArrayList<>(); memberList.add(user.getId()); } else if (!memberList.contains(user.getId())) { memberList.add(user.getId()); } } public void removeMember(User user) { if (memberList != null && !memberList.isEmpty()) { memberList.remove(user.getId()); } } /** * This function updates the parent and the ancestors. * @param parent The new parent. */ public void setParent(Division parent) { logger.trace("Changing parent for " + this.name); if (parent != null) { logger.debug("Updating ancestors for " + this.name); List<Division> ancestor; if (parent.getAncestors() == null) { ancestor = new ArrayList<>(); } else { //Need to create a new ArrayList, assigning would lead to fill and use BOTH lists ancestor = new ArrayList<>(parent.getAncestors()); } ancestor.add(parent); logger.debug("Ancestors " + ancestor.stream().map(Division::getName).collect(Collectors.joining(", ")) + " for division " + this.name); this.ancestors = ancestor; } this.parent = parent; logger.info("Successfully updated parent and ancestors of " + this.name); } /** * @return The ancestor of this object. The function never returns null, but an empty list, if there are no ancestors defined. */ public List<Division> getAncestors() { if (ancestors == null) { return new ArrayList<>(); } return ancestors; } public void addAncestor(Division ancestor) { logger.debug("Adding ancestor " + ancestor.getName() + " to " + this.name); this.getAncestors().add(ancestor); } public void setAncestors(List<Division> ancestors) { this.ancestors = ancestors; } /** * This function creates a new division object and copies only the id of the current division. * @return A new division object only containing the id. */ @JsonIgnore @Transient public Division getSendingObjectOnlyId() { Division sendingObject = new Division(); sendingObject.setId(id); return sendingObject; } /** * This function removes all fields that the other users of the app are not allowed to see. * @return A copied division object, without the fields, other users are not allowed to see. */ @JsonIgnore @Transient public Division getSendingObjectInternalSync() { return getSendingObject(); } /** * This function creates a sending-save object (ensuring there is no infinite loop caused by references) * @return A sending-save instance of the object. */ @JsonIgnore @Transient public Division getSendingObject() { Division sendingObject = getSendingObject(new String[0]); if (sendingObject.getAdminUser() != null) { sendingObject.setAdminUser(sendingObject.getAdminUser().getSendingObjectOnlyEmailNameId()); } return sendingObject; } /** * This function copies the current object, ignoring the member fields specified by the ignored properties vararg. * @param ignoredProperties The member fields ignored during the copying. * @return A copy of the current object, not containing information about the ignored properties. */ @JsonIgnore @Transient private Division getSendingObject(String... ignoredProperties) { Division sendingObject = new Division(); BeanUtils.copyProperties(this, sendingObject, ignoredProperties); return sendingObject; } //Todo: These two are expensive!! Especially expanded set, which is called fairly often. /** * This function is using a set of divisions, and reduces it to the divisions closest to the root * @param unoptimizedSetOfDivisions A set of divisions. * @return The list of optimized divisions. */ @JsonIgnore @Transient public static List<Division> getOptimizedSetOfDivisions(List<Division> unoptimizedSetOfDivisions) { if (unoptimizedSetOfDivisions == null || unoptimizedSetOfDivisions.isEmpty()) { logger.warn("Trying to optimize set of divisions, but unoptimized set is either null or empty"); return null; } else if (unoptimizedSetOfDivisions.size() == 1) { return unoptimizedSetOfDivisions; } else { logger.debug("Optimizing division set"); //Reducing the list to the divisions that are on the top of the tree, removing all unnecessary divisions. return unoptimizedSetOfDivisions.parallelStream() //Creating a stream of all divisions .filter(division -> unoptimizedSetOfDivisions.parallelStream().sorted() //filtering all divisions that are already defined in a divisions that is closer to the root of the tree. Using a parallel and sorted stream, because therefore the likeliness of an early match increases .noneMatch(allDivisions -> division.getAncestors().contains(allDivisions))) //Checking, if there is any division in the list, that is an ancestor of the current division. If there is a match there exists a closer division. .collect(Collectors.toList()); // Converting the stream to a list } } /** * This function expands the set of divisions. This means that every division, the user is part of (all child divisions of every division) are going to be returned. * @param initialSetOfDivisions The set of divisions that needs to be expanded. * @param divisionRepository The division repository, needed to get queried. * @return The expanded list of divisions. */ @JsonIgnore @Transient public static List<Division> getExpandedSetOfDivisions(List<Division> initialSetOfDivisions, DivisionRepository divisionRepository) { if ((initialSetOfDivisions = getOptimizedSetOfDivisions(initialSetOfDivisions)) == null) { logger.warn("Trying to expand a set of divisions, but initial set is either null or empty"); return null; } else { logger.debug("Expanding division set"); HashSet<Division> expandedSetOfDivisions = new HashSet<>(); for (Division division : initialSetOfDivisions) { expandedSetOfDivisions.addAll(divisionRepository.findByAncestors(division)); expandedSetOfDivisions.add(divisionRepository.findById(division.getId())); } return new ArrayList<>(expandedSetOfDivisions); } } /** * Comparing two objects of the division class according to their name. Overwritten to be able to use the contains() method of java.util.List. * @param obj The object compared to the current object. * @return True if the two objects are equal, false otherwise. */ @Override public boolean equals(Object obj) { return obj != null && obj instanceof Division && this.id != null && this.id.equals(((Division) obj).getId()); } @Override public int hashCode() { return id == null ? 0 : id.hashCode(); } @Override public String toString() { return name != null && !name.isEmpty() ? name : id; } /** * This function is comparable to other divisions according to their distance to the root node. * @param o The division which is compared to the current division. * @return A negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. If o is null, 0 is returned. */ @Override public int compareTo(Division o) { if (o != null) { try { List<Division> thisAncestors = this.getAncestors(), otherAncestors = o.getAncestors(); if (thisAncestors == null || thisAncestors.isEmpty()) { return 1; } else if (otherAncestors == null || otherAncestors.isEmpty()) { return -1; } else { return Integer.compare(thisAncestors.size(), otherAncestors.size()); } } catch (LazyLoadingException e) { logger.error("Unable to compare divisions {} {}: {}", o, this, e.getLocalizedMessage()); return 0; } } else { logger.error("Comparing {} with null division", this); return 0; } } }