Package org.projectforge.task

Source Code of org.projectforge.task.TaskNode

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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 org.projectforge.task;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.log4j.Logger;
import org.dom4j.Element;
import org.projectforge.access.AccessType;
import org.projectforge.access.GroupTaskAccessDO;
import org.projectforge.access.OperationType;
import org.projectforge.core.IdObject;
import org.projectforge.core.UserException;
import org.projectforge.fibu.ProjektDO;

/**
* Represents a single task as part of the TaskTree. The data of a task node is stored in the database.
* @author Kai Reinhard (k.reinhard@micromata.de)
*/
public class TaskNode implements IdObject<Integer>, Serializable
{
  private static final long serialVersionUID = -3759574521842841341L;

  /** For log messages. */
  private static final Logger log = Logger.getLogger(TaskNode.class);

  /** Reference to the parent task node with the parentTaskID. */
  TaskNode parent = null;

  ProjektDO projekt;

  /** Total duration of all time sheets of this task (excluding the child tasks) in seconds. */
  long totalDuration = 0;

  /**
   * Sum of all ordered person days excluding descendant nodes. Ordered person days are defined by the sum of all assigned order position's
   * person days. Used and set by task tree.
   */
  BigDecimal orderedPersonDays;

  /**
   * References to all child nodes in an ArrayList from element typ TaskNode.
   */
  List<TaskNode> childs = null;

  /** The data of this TaskNode. */
  TaskDO task = null;

  boolean bookableForTimesheets;

  /**
   * For every group with access to this node the permissions will be stored here.
   */
  private final List<GroupTaskAccessDO> groupTaskAccessList = new ArrayList<GroupTaskAccessDO>();

  public TaskNode()
  {
  }

  /**
   * @return True, if the parent task id of the underlying task is null, false otherwise.
   */
  public boolean isRootNode()
  {
    return this.task.getParentTaskId() == null;
  }

  public void setTask(final TaskDO task)
  {
    this.task = task;
  }

  public TaskDO getTask()
  {
    return this.task;
  }

  /** The id of this task given by the database. */
  public Integer getId()
  {
    return task.getId();
  }

  /** The id of this task given by the database. */
  public Integer getTaskId()
  {
    return task.getId();
  }

  public Integer getParentId()
  {
    if (parent == null) {
      return null;
    }
    return parent.getId();
  }

  /** Returns the parent task. */
  public TaskNode getParent()
  {
    return this.parent;
  }

  public void internalSetParent(final TaskNode parent)
  {
    this.parent = parent;
    if (parent != null) {
      parent.addChild(this);
    }
  }

  /**
   * @return The reference of the assigned task, or if not given or blank of the parent task node. If the parent task node has no reference
   *         the grand parent task reference will be assumed and so on.
   */
  public String getReference()
  {
    if (StringUtils.isNotBlank(task.getReference()) == true) {
      return task.getReference();
    } else if (parent != null) {
      return parent.getReference();
    } else {
      return "";
    }
  }

  /**
   * Gets the project, which is assigned to the task or if not found to the parent task or grand parent task etc.
   * @param taskId
   * @return null, if now project is assigned to this task or ancestor tasks.
   * @see ProjektDO#getTask()
   */
  public ProjektDO getProjekt()
  {
    return getProjekt(true);
  }

  /**
   * Gets the project, which is assigned to the task or if not found to the parent task or grand parent task etc.
   * @param taskId
   * @param recursive If true then search the ancestor nodes for a given project.
   * @return null, if now project is assigned to this task or ancestor tasks.
   * @see ProjektDO#getTask()
   */
  public ProjektDO getProjekt(final boolean recursive)
  {
    if (projekt != null) {
      return projekt;
    } else if (recursive == true && parent != null) {
      return parent.getProjekt();
    } else {
      return null;
    }
  }

  public boolean isDeleted()
  {
    return task.isDeleted();
  }

  /**
   * @return True if this node is closed/deleted or any ancestor node is closed/deleted.
   */
  public boolean isFinished()
  {
    if (task.isDeleted() || task.getStatus() == TaskStatus.C) {
      return true;
    }
    if (parent != null) {
      return parent.isFinished();
    }
    return false;
  }

  /**
   * @return the bookableForTimesheets
   */
  public boolean isBookableForTimesheets()
  {
    return bookableForTimesheets;
  }

  public List<Integer> getDescendantIds()
  {
    final List<Integer> descendants = new ArrayList<Integer>();
    getDescendantIds(descendants);
    return descendants;
  }

  private void getDescendantIds(final List<Integer> descendants)
  {
    if (this.childs != null) {
      for (final TaskNode node : this.childs) {
        if (descendants.contains(node.getId()) == false) {
          // Paranoia setting for cyclic references.
          descendants.add(node.getId());
          node.getDescendantIds(descendants);
        }
      }
    }
  }

  public List<Integer> getAncestorIds()
  {
    final List<Integer> ancestors = new ArrayList<Integer>();
    getAncestorIds(ancestors);
    return ancestors;
  }

  private void getAncestorIds(final List<Integer> ancestors)
  {
    if (this.parent != null) {
      if (ancestors.contains(this.parent.getId()) == false) {
        // Paranoia setting for cyclic references.
        ancestors.add(this.parent.getId());
        this.parent.getAncestorIds(ancestors);
      }
    }
  }

  /**
   * Returns all childs of this task in an ArrayList with elements from type TaskNode.
   */
  public List<TaskNode> getChilds()
  {
    return this.childs;
  }

  /** Has this task any childs? */
  public boolean hasChilds()
  {
    return this.childs != null && this.childs.size() > 0 ? true : false;
  }

  /** Checks if the given node is a child / descendant of this node. */
  public boolean isParentOf(final TaskNode node)
  {
    if (this.childs == null) {
      return false;
    }
    for (final TaskNode child : this.childs) {
      if (child.equals(node) == true) {
        return true;
      } else if (child.isParentOf(node) == true) {
        return true;
      }
    }
    return false;
  }

  /**
   * Returns the path to the root node in an ArrayList. The list contains also the current task.
   */
  public List<TaskNode> getPathToRoot()
  {
    return getPathToAncestor(null);
  }

  /**
   * Returns the path to the parent node in an ArrayList.
   */
  public List<TaskNode> getPathToAncestor(final Integer ancestorTaskId)
  {
    if (this.parent == null || this.task.getId().equals(ancestorTaskId) == true) {
      return new ArrayList<TaskNode>();
    }
    final List<TaskNode> path = this.parent.getPathToAncestor(ancestorTaskId);
    path.add(this);
    return path;
  }

  /**
   * Sets / changes the parent of this node. This method does not modify the parent task! So it should be called only by TaskTree.
   */
  void setParent(final TaskNode parent)
  {
    if (parent != null) {
      if (parent.getId().equals(getId()) == true || this.isParentOf(parent)) {
        log.error("Oups, cyclic reference detection: taskId = " + getId() + ", parentTaskId = " + parent.getId());
        throw new UserException(TaskDao.I18N_KEY_ERROR_CYCLIC_REFERENCE);
      }
      this.parent = parent;
      this.task.setParentTask(parent.getTask());
    }
  }

  /**
   * Adds a new task as a child of this node. It does not check wether this task already exist as child or not! This method does not modify
   * the child task!
   */
  void addChild(final TaskNode child)
  {
    if (child != null) {
      if (child.getId().equals(getId()) == true || child.isParentOf(this)) {
        log.error("Oups, cyclic reference detection: taskId = " + getId() + ", parentTaskId = " + parent.getId());
        return;
      }
      if (this.childs == null) {
        this.childs = new ArrayList<TaskNode>();
      }
      this.childs.add(child);
    }
  }

  /**
   * Removes a child task of this node. This method does not modify the child task!
   */
  void removeChild(final TaskNode child)
  {
    if (child == null) {
      log.error("Oups, child is null, can't remove it from parent.");
    } else if (this.childs == null) {
      log.error("Oups, this node has no childs to remove.");
    } else if (this.childs.contains(child) == false) {
      log.error("Oups, this node doesn't contain given child.");
    } else {
      log.debug("Removing child " + child.getTaskId() + " from parent " + this.getTaskId());
      this.childs.remove(child);
    }
  }

  /**
   * Checks the desired permission for the given group to this task. If no GroupTaskAccess is defined for this task for the given group,
   * hasPermission will be called of the parent task.
   *
   * @param groupId The id of the group to check.
   * @param accessType TASK_ACCESS, ...
   * @param opType Select, insert, update or delete.
   * @see AccessType
   * @see OperationType
   */
  public boolean hasPermission(final Integer groupId, final AccessType accessType, final OperationType opType)
  {
    final GroupTaskAccessDO groupAccess = getGroupTaskAccess(groupId);
    if (groupAccess == null) {
      if (parent != null) {
        return parent.isPermissionRecursive(groupId) && parent.hasPermission(groupId, accessType, opType);
      }
      // This is the root node.
      return false;
    }
    return groupAccess.hasPermission(accessType, opType);
  }

  public boolean isPermissionRecursive(final Integer groupId)
  {
    final GroupTaskAccessDO groupAccess = getGroupTaskAccess(groupId);
    return groupAccess == null || groupAccess.isRecursive() == true;
  }

  /**
   * Gets the GroupTaskAccessDO for the given group.
   * @param groupId
   * @return The GroupTaskAccessDO or null if not exists.
   */
  GroupTaskAccessDO getGroupTaskAccess(final Integer groupId)
  {
    Validate.notNull(groupId);
    for (final GroupTaskAccessDO access : groupTaskAccessList) {
      if (groupId.equals(access.getGroupId()) == true) {
        return access;
      }
    }
    return null;
  }

  /**
   * Sets the task group access to this task node for the given group. Removes any previous stored GroupTaskAccessDO for the same group if
   * exists. Multiple GroupTaskAccessDO entries for one group will be avoided.
   * @param GroupTaskAccessDO
   */
  void setGroupTaskAccess(final GroupTaskAccessDO groupTaskAccess)
  {
    Validate.isTrue(ObjectUtils.equals(this.getTaskId(), groupTaskAccess.getTaskId()) == true);
    // TODO: Should be called after update and insert into database.
    if (log.isInfoEnabled() == true) {
      log.debug("Set explicit access, taskId = " + getTaskId() + ", groupId = " + groupTaskAccess.getGroupId());
    }
    synchronized (groupTaskAccessList) {
      removeGroupTaskAccess(groupTaskAccess.getGroupId());
      groupTaskAccessList.add(groupTaskAccess);
    }
  }

  /**
   * Removes the GroupTaskAccessDO for the given group if exists.
   * @param groupId
   * @return true if an entry was found and removed, otherwise false.
   */
  boolean removeGroupTaskAccess(final Integer groupId)
  {
    // TODO: Should be called after deleting from database.
    Validate.notNull(groupId);
    boolean result = false;
    synchronized (groupTaskAccessList) {
      final Iterator<GroupTaskAccessDO> it = groupTaskAccessList.iterator();
      while (it.hasNext() == true) {
        final GroupTaskAccessDO access = it.next();
        if (groupId.equals(access.getGroupId()) == true) {
          it.remove();
          result = true;
        }
      }
    }
    return result;
  }

  /**
   * Gets the total duration of all time sheets in seconds.
   * @param recursive If true, then the durations of all time sheets of the sub tasks will be added.
   * @return
   */
  public long getDuration(final TaskTree taskTree, final boolean recursive)
  {
    if (totalDuration < 0) {
      taskTree.readTotalDuration(this.getId());
    }
    if (recursive == false || childs == null) {
      return totalDuration;
    }
    long duration = totalDuration;
    for (final TaskNode child : childs) {
      duration += child.getDuration(taskTree, true);
    }
    return duration;
  }

  @Override
  public boolean equals(final Object o)
  {
    if (o instanceof TaskNode) {
      final TaskNode other = (TaskNode) o;
      return ObjectUtils.equals(this.getParentId(), other.getParentId()) == true
          && ObjectUtils.equals(this.getTask().getTitle(), other.getTask().getTitle()) == true;
    }
    return false;
  }

  @Override
  public int hashCode()
  {
    final HashCodeBuilder hcb = new HashCodeBuilder();
    hcb.append(this.getParentId()).append(this.getTask().getTitle());
    return hcb.toHashCode();
  }

  @Override
  public String toString()
  {
    final ToStringBuilder sb = new ToStringBuilder(this);
    sb.append("id", getId());
    Object parentId = null;
    if (this.parent != null) {
      parentId = this.parent.getId();
    }
    log.debug("id: " + this.getId() + ", parentId: " + parentId);
    sb.append("parent", parentId);
    sb.append("title", task.getTitle());
    sb.append("childs", this.childs);
    return sb.toString();
  }

  Element addXMLElement(final Element parent)
  {
    final Element el = parent.addElement("task").addAttribute("id", String.valueOf(this.getId()))
        .addAttribute("name", this.task.getTitle());
    if (this.childs != null) {
      for (final TaskNode node : this.childs) {
        node.addXMLElement(el);
      }
    }
    return el;
  }
}
TOP

Related Classes of org.projectforge.task.TaskNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.