de.iteratec.iteraplan.model.AbstractHierarchicalEntity.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.model.AbstractHierarchicalEntity.java

Source

/*
 * iteraplan is an IT Governance web application developed by iteratec, GmbH
 * Copyright (C) 2004 - 2014 iteratec, GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY ITERATEC, ITERATEC DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 *
 * 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 Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact iteratec GmbH headquarters at Inselkammerstr. 4
 * 82008 Munich - Unterhaching, Germany, or at email address info@iteratec.de.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "iteraplan" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by iteraplan".
 */
package de.iteratec.iteraplan.model;

import static de.iteratec.iteraplan.common.util.CollectionUtils.arrayList;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.persistence.Entity;

import org.apache.commons.lang.StringUtils;
import org.hibernate.envers.AuditMappedBy;
import org.hibernate.envers.Audited;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Store;

import com.google.common.base.Objects;
import com.google.common.collect.Sets;

import de.iteratec.iteraplan.common.Constants;
import de.iteratec.iteraplan.common.GeneralHelper;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.MessageAccess;
import de.iteratec.iteraplan.common.UserContext;
import de.iteratec.iteraplan.common.error.IteraplanBusinessException;
import de.iteratec.iteraplan.common.error.IteraplanErrorMessages;
import de.iteratec.iteraplan.common.util.Preconditions;
import de.iteratec.iteraplan.common.util.StringUtil;
import de.iteratec.iteraplan.model.interfaces.HierarchicalEntity;
import de.iteratec.iteraplan.model.sorting.HierarchyHelper;

/**
 * Base class for hierarchical entities.
 *
 * @param <T>
 *          Type parameter.
 */
@Entity
@Audited
public abstract class AbstractHierarchicalEntity<T extends HierarchicalEntity<T>> extends BuildingBlock
        implements HierarchicalEntity<T> {

    private static final long serialVersionUID = -2122595039285381144L;

    private static final Logger LOGGER = Logger.getIteraplanLogger(AbstractHierarchicalEntity.class);

    public static final String TOP_LEVEL_NAME = "-";

    /** {@link #getName()} */
    @Field(store = Store.YES)
    private String name;

    /** {@link #getDescription()} */
    @Field(store = Store.YES)
    private String description;

    private T parent = null;
    private List<T> children = new ArrayList<T>();
    private Integer position;

    public AbstractHierarchicalEntity() {
        super();
    }

    /**
     * {@inheritDoc}
     */
    public List<T> getChildrenAsList() {
        return getChildren();
    }

    /**
     * {@inheritDoc}
     */
    public Set<T> getChildrenAsSet() {
        return new HashSet<T>(getChildren());
    }

    public void getDescendants(T entity, Set<T> set) {

        for (T elem : entity.getChildrenAsSet()) {
            set.add(elem);
            getDescendants(elem, set);
        }
    }

    /**
     * Returns the description of this hierarchical entity. This method takes special measures if the
     * current hierarchical element is the virtual top-level element. In that case, the key to an
     * internationalized description provided for each concrete subclass is returned via the abstract
     * method {@link #getI18NKey()}. Together with the current locale stored in the user's context the
     * key is used to retrieve the internationalized description from the appropriate resource bundle.
     *
     * @return The description.
     */
    @Override
    public String getDescription() {
        if (isTopLevelElement()) {
            Locale locale = UserContext.getCurrentLocale();
            if (locale != null) {
                String desc = MessageAccess.getStringOrNull(getI18NKey(), locale);
                if (desc != null) {
                    return desc;
                }
            }
            return getI18NKey();
        }
        return description;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getHierarchicalName() {
        return makeHierarchicalName();
    }

    public String getHierarchicalNameIfDifferent() {
        if (this.name.equals(this.getHierarchicalName())) {
            return "";
        } else {
            return this.getHierarchicalName();
        }
    }

    /**
     * Returns the localized string for the description of the hierarchy's top-level element.
     *
     * @return The key to the localized description of the top-level element.
     */
    public abstract String getI18NKey();

    /**
     * {@inheritDoc} The identity string returned for hierarchical entities equals their hierarchical
     * name.
     */
    public String getIdentityString() {
        return getHierarchicalName();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public int getLevel() {

        T entity = (T) this;
        int level = 0;
        while (entity.getParentElement() != null) {
            level++;
            entity = entity.getParentElement();
        }

        return level;
    }

    /**
     * @return Returns the name of this hierarchical entity.
     */
    public String getName() {
        return name;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getNonHierarchicalName() {
        return name;
    }

    /** {@inheritDoc} */
    public boolean isTopLevelElement() {
        return TOP_LEVEL_NAME.equals(name);
    }

    /**
     * Assembles and returns the hierarchical name for the given HierarchicalEntity.
     *
     * @param entity
     *          The entity for which the hierarchical name should be created.
     * @param detailedName
     *          Iff true, the full hierarchical name is returned. Else, a short version is returned.
     * @return The hierarchical name where each part is separated by an appropriate separator symbol.
     */
    private String makeHierarchicalName() {
        if (getId() == null) {
            return "";
        }

        List<String> stringsToConcatenate = new ArrayList<String>();
        stringsToConcatenate.add(getNonHierarchicalName());

        HierarchicalEntity<?> p = getParentElement();
        while (p != null) {
            if (p.getParentElement() != null) {
                stringsToConcatenate.add(0, p.getNonHierarchicalName());
            }
            p = p.getParentElement();
        }

        return GeneralHelper.makeConcatenatedStringWithSeparator(stringsToConcatenate, HIERARCHICAL_NAME_SEPARATOR);
    }

    public void setDescription(String description) {
        // Ignore description for virtual elements as it is immutable
        if (!isTopLevelElement()) {
            this.description = StringUtil.removeIllegalXMLChars(description);
        }
    }

    public void setName(String name) {
        this.name = StringUtils.trim(StringUtil.removeIllegalXMLChars(name));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void validate() {
        super.validate();

        // Check for cycles in the parent/child hierarchy.
        if (HierarchyHelper.hasElementOfCycle(this)) {
            throw new IteraplanBusinessException(IteraplanErrorMessages.ELEMENT_OF_HIERARCHY_CYCLE);
        }

        if ((name == null) || name.equals("")) {
            throw new IteraplanBusinessException(IteraplanErrorMessages.NAME_CANNOT_BE_EMPTY);
        }

        if ((description != null) && (description.length() > Constants.TEXT_LONG)) {
            throw new IteraplanBusinessException(IteraplanErrorMessages.TEXT_TOO_LONG);
        }

        // If this element has got no parent, it must be the top level element.
        if ((getParentElement() == null) && !isTopLevelElement()) {
            throw new IteraplanBusinessException(IteraplanErrorMessages.PARENT_ELEMENT_UNDEFINED);
        }
    }

    public int findChildPos(T c) {
        List<T> childrenList = getChildrenAsList();
        for (int result = 0; result < childrenList.size(); result++) {
            if (childrenList.get(result) != null && Objects.equal(childrenList.get(result).getId(), c.getId())) {
                return result;
            }
        }

        LOGGER.error(
                "Object hierarchy inconsistent. Child {0} is not a child of Parent {1}! Can happen, if elements get deleted.",
                c, this);
        throw new IllegalStateException(
                "Object hierarchy inconsistent. Child " + c + " is not a child of Parent " + this);
    }

    /**
     * Returns this element's position in the parents children list. Only used internally by Hibernate for List order persistence.
     * Do not call directly!
     */
    public Integer getPosition() {
        return position;
    }

    /**
     * Only used by Hibernate for List order persistence. Do not call directly!
     * @param pos
     */
    public void setPosition(Integer pos) {
        this.position = pos;
    }

    /**
     * {@inheritDoc}
     */
    public T getParentElement() {
        return parent;
    }

    public T getParent() {
        return parent;
    }

    @AuditMappedBy(mappedBy = "parent", positionMappedBy = "position")
    public List<T> getChildren() {
        return children;
    }

    public void setParent(T newParent) {
        LOGGER.debug("Setting Parent");

        LOGGER.debug("I am: {0}", this);

        T oldParent = this.parent;
        LOGGER.debug("old parent: {0}", oldParent);

        LOGGER.debug("new parent: {0}", newParent);

        this.parent = newParent;
        // perform cycle check to validate that the hierarchy is still in a consistent state
        // this is explicitly done _after_ the new parent relation has been set
        if (HierarchyHelper.hasElementOfCycle(this) || this.equals(newParent)) {
            LOGGER.error("Can't set parent of {0} to {1}", this, newParent);
            // reset to the old, valid parent
            this.parent = oldParent;
            throw new IteraplanBusinessException(IteraplanErrorMessages.ELEMENT_OF_HIERARCHY_CYCLE);
        }
    }

    /**
     * Adds a parent, or removes it if {@code parentToAdd} is null. Updates both sides of the association and
     * removes <code>this</code> from the old parent's children list.
     * <p>
     * Do not iterate over a child-list of an entity and call this method on the children. It will
     * result in a concurrent modification exception. Instead copy the children into a new list first
     * and iterate over the new list.
     * </p>
     * @param parentToAdd
     *          element to add as parent
     */
    @SuppressWarnings("unchecked")
    public void addParent(T parentToAdd) {

        LOGGER.debug("parentToAdd: {0}", parentToAdd);

        Preconditions.checkNotNull(parentToAdd);
        T oldParent = this.parent;
        setParent(parentToAdd);
        // if setParent works (doesn't throw an exception), update children-lists
        removeFromParent(oldParent);
        // it's save to cast here, there seems to be no better solution
        parentToAdd.getChildren().add((T) this);
    }

    /**
     * Removes the parent. Updates both sides of the association.
     */
    public void removeParent() {
        LOGGER.debug("called removeParent");
        LOGGER.debug("I am: {0}", this.getName());
        LOGGER.debug("Parent until now is: {0}", parent);

        if (parent != null) {

            parent.getChildren().remove(this);
            setParent(null);
        }
    }

    /**
     * Removes this from the parent's children list.
     */
    public void removeFromParent(T parentElement) {
        LOGGER.debug("removeFromParent: {0}", parentElement);
        LOGGER.debug("I am: {0}", getName());
        if (parentElement != null) {
            parentElement.getChildren().remove(this);
        }
    }

    public void setChildren(List<T> children) {
        this.children = children;
    }

    /**
     * Removes all Children
     */
    public void removeAllChildren() {
        List<T> newList = arrayList();
        this.children = newList;
    }

    @Override
    public int hashCode() {
        int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((description == null) ? 0 : description.hashCode());
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!super.equals(obj)) {
            return false;
        }
        if (!(obj instanceof AbstractHierarchicalEntity<?>)) {
            return false;
        }
        final AbstractHierarchicalEntity<?> other = (AbstractHierarchicalEntity<?>) obj;
        if (getDescription() == null) {
            if (other.getDescription() != null) {
                return false;
            }
        } else if (!getDescription().equals(other.getDescription())) {
            return false;
        }
        if (getName() == null) {
            if (other.getName() != null) {
                return false;
            }
        } else if (!getName().equals(other.getName())) {
            return false;
        }
        return true;
    }

    public void removeChildren(T root) {
        for (T child : Sets.newHashSet(getChildren())) {
            child.removeParent();
            child.addParent(root);
        }
        getChildren().clear();
    }

    @SuppressWarnings("unchecked")
    public void addChildren(Collection<T> childrenToAdd) {
        for (T child : childrenToAdd) {
            child.removeParent();
            child.addParent((T) this);
        }
    }

}