Package org.projectforge.task

Source Code of org.projectforge.task.TaskTree

/////////////////////////////////////////////////////////////////////////////
//
// 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.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.Validate;
import org.apache.log4j.Logger;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.hibernate.Hibernate;
import org.projectforge.access.AccessDao;
import org.projectforge.access.GroupTaskAccessDO;
import org.projectforge.access.OperationType;
import org.projectforge.common.AbstractCache;
import org.projectforge.common.DateHelper;
import org.projectforge.common.NumberHelper;
import org.projectforge.core.InternalErrorException;
import org.projectforge.debug.StackTraceHolder;
import org.projectforge.fibu.AuftragDao;
import org.projectforge.fibu.AuftragsPositionVO;
import org.projectforge.fibu.ProjektDO;
import org.projectforge.fibu.ProjektDao;
import org.projectforge.fibu.kost.Kost2DO;
import org.projectforge.fibu.kost.KostCache;
import org.projectforge.registry.Registry;
import org.projectforge.timesheet.TimesheetDO;
import org.projectforge.timesheet.TimesheetDao;

/**
* Holds the complete task list in a tree. It will be initialized by the values read from the database. Any changes will be written to this
* tree and to the database.
* @author Kai Reinhard (k.reinhard@micromata.de)
*/
public class TaskTree extends AbstractCache implements Serializable
{
  private static final long serialVersionUID = 3748005966442878168L;

  @SpringBean(name = "taskDao")
  private transient TaskDao taskDao;

  @SpringBean(name = "accessDao")
  private transient AccessDao accessDao;

  @SpringBean(name = "kost2Dao")
  private transient ProjektDao projektDao;

  @SpringBean(name = "kostCache")
  private transient KostCache kostCache;

  @SpringBean(name = "auftragDao")
  private transient AuftragDao auftragDao;

  private static final List<TaskNode> EMPTY_LIST = new ArrayList<TaskNode>();

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

  /** Time of last modification in milliseconds from 1970-01-01. */
  private long timeOfLastModification = 0;

  /** For faster searching of entries. */
  private Map<Integer, TaskNode> taskMap;

  /** The root node of all tasks. The only node with parent null. */
  private TaskNode root = null;

  private Map<Integer, Set<AuftragsPositionVO>> orderPositionReferences;

  private boolean orderPositionReferencesDirty = true;

  public TaskNode getRootTaskNode()
  {
    checkRefresh();
    return this.root;
  }

  /** Adds the given node as child of the given parent. */
  private synchronized TaskNode addTaskNode(final TaskNode node, final TaskNode parent)
  {
    checkRefresh();
    if (parent != null) {
      node.setParent(parent);
      parent.addChild(node);
    }
    updateTimeOfLastModification();
    return node;
  }

  /**
   * Adds a new node with the given data. The given Task holds all data and the information (id) of the parent node of the node to add. Will
   * be called by TaskDAO after inserting a new task.
   */
  TaskNode addTaskNode(final TaskDO task)
  {
    checkRefresh();
    final TaskNode node = new TaskNode();
    node.setTask(task);
    final TaskNode parent = getTaskNodeById(task.getParentTaskId());
    if (parent != null) {
      node.setParent(parent);
    } else if (root == null) {
      // this is the root node:
      root = node;
    } else if (node.getId().equals(root.getId()) == false) {
      // This node is not the root node:
      node.setParent(root);
    }
    taskMap.put(node.getId(), node);
    final TimesheetDao timesheetDao = Registry.instance().getDao(TimesheetDao.class);
    final TimesheetDO timesheet = new TimesheetDO().setTask(task);
    final boolean bookable = timesheetDao.checkTaskBookable(timesheet, null, OperationType.INSERT, false);
    node.bookableForTimesheets = bookable;
    return addTaskNode(node, parent);
  }

  /**
   * @param taskId
   * @param ancestorTaskId
   * @return
   * @see TaskNode#getPathToAncestor(Integer)
   */
  public List<TaskNode> getPath(final Integer taskId, final Integer ancestorTaskId)
  {
    checkRefresh();
    if (taskId == null) {
      return EMPTY_LIST;
    }
    final TaskNode taskNode = getTaskNodeById(taskId);
    if (taskNode == null) {
      return EMPTY_LIST;
    }
    return taskNode.getPathToAncestor(ancestorTaskId);
  }

  /**
   * Returns the path to the root node in an ArrayList.
   * @see #getPath(Integer, Integer)
   */
  public List<TaskNode> getPathToRoot(final Integer taskId)
  {
    return getPath(taskId, null);
  }

  /** All task nodes are stored in an HashMap for faster searching. */
  public TaskNode getTaskNodeById(final Integer id)
  {
    if (id == null) {
      return null;
    }
    checkRefresh();
    return taskMap.get(id);
  }

  public TaskDO getTaskById(final Integer id)
  {
    checkRefresh();
    final TaskNode node = getTaskNodeById(id);
    if (node != null) {
      return node.getTask();
    }
    return null;
  }

  /**
   * 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.
   */
  public ProjektDO getProjekt(final Integer taskId)
  {
    if (taskId == null) {
      return null;
    }
    final TaskNode node = getTaskNodeById(taskId);
    if (node != null) {
      return node.getProjekt();
    } else {
      return null;
    }
  }

  public void internalSetProject(final Integer taskId, final ProjektDO projekt)
  {
    final TaskNode node = getTaskNodeById(taskId);
    if (node == null) {
      throw new InternalErrorException("Could not found task with id " + taskId + " in internalSetProject");
    }
    node.projekt = projekt;
  }

  /**
   * recursive = true.
   * @param taskId
   * @return
   * @see #getKost2List(Integer, boolean)
   */
  public List<Kost2DO> getKost2List(final Integer taskId)
  {
    final TaskNode node = getTaskNodeById(taskId);
    return getKost2List(node, true);
  }

  /**
   * Get the available and active Kost2DOs of the task, or if not available of the first found ancestor tasks if available. Kost2 are
   * defined over assigned projects and kost2s. If project or Kost2DO not assigned for a task, then the project or task of the parent task
   * will be assumed. If the parent task has no project or task the grand parent task will be taken and so on (recursive until root task).
   * @param taskId
   * @param recursive If true then search the ancestor task for cost definitions if current task haven't.
   * @return Available Kost2DOs or null, if no Kost2DO found.
   */
  public List<Kost2DO> getKost2List(final Integer taskId, final boolean recursive)
  {
    final TaskNode node = getTaskNodeById(taskId);
    return getKost2List(node, recursive);
  }

  /**
   *
   * @param projekt If not initialized then the project is get from the data base.
   * @param task Only needed for output if an entry (Kost2) of the blackWhiteList cannot be found.
   * @param blackWhiteList
   * @param kost2IsBlackList
   * @return
   */
  public List<Kost2DO> getKost2List(ProjektDO projekt, final TaskDO task, final String[] blackWhiteList, final boolean kost2IsBlackList)
  {
    final List<Kost2DO> kost2List = new ArrayList<Kost2DO>();
    final boolean wildcard = blackWhiteList != null && blackWhiteList.length == 1 && "*".equals(blackWhiteList[0]);
    if (projekt != null && Hibernate.isPropertyInitialized(projekt, "kunde") == false) {
      projekt = projektDao.internalGetById(projekt.getId());
    }
    if (projekt != null) {
      final List<Kost2DO> list = kostCache.getActiveKost2(projekt.getNummernkreis(), projekt.getBereich(), projekt.getNummer());
      if (CollectionUtils.isNotEmpty(list) == true) {
        for (final Kost2DO kost2 : list) {
          if (wildcard == true) { // black-white-list is "*".
            if (kost2IsBlackList == true) {
              break; // Do not add any entry.
            } else {
              kost2List.add(kost2); // Add all entries.
            }
          } else if (blackWhiteList == null || blackWhiteList.length == 0) {
            // Add all (either black nor white entry is given):
            kost2List.add(kost2);
          } else {
            final String no = kost2.getFormattedNumber();
            boolean add = kost2IsBlackList; // false for white list and true for black list at default.
            for (final String item : blackWhiteList) {
              if (no.endsWith(item) == true) {
                if (kost2IsBlackList == true) {
                  // Black list entry matches, so do not add entry:
                  add = false;
                  break;
                } else {
                  // White list entry matches, so add entry:
                  add = true;
                  break;
                }
              }
            }
            if (add == true) {
              kost2List.add(kost2);
            }
          }
        }
      }
    } else if (kost2IsBlackList == false && blackWhiteList != null) {
      // Add all given KoSt2DOs.
      for (final String item : blackWhiteList) {
        final Kost2DO kost2 = kostCache.getKost2(item);
        if (kost2 != null) {
          kost2List.add(kost2);
        } else {
          log.info("Given kost2 not found: '" + item + "'. Specified at task " + task.getId() + " - " + task);
        }
      }
    }
    if (CollectionUtils.isNotEmpty(kost2List) == true) {
      Collections.sort(kost2List);
      return kost2List;
    } else {
      return null;
    }
  }

  private List<Kost2DO> getKost2List(final TaskNode node, final boolean recursive)
  {
    if (node == null) {
      return null;
    }
    final TaskDO task = node.getTask();
    final String[] blackWhiteList = task.getKost2BlackWhiteItems();
    final ProjektDO projekt = node.getProjekt(blackWhiteList != null); // If black-white-list is null then do not search for projekt of
    // ancestor tasks.
    final List<Kost2DO> list = getKost2List(projekt, task, blackWhiteList, task.isKost2IsBlackList());
    if (list != null) {
      return list;
    } else if (node.parent != null && recursive == true) {
      return getKost2List(node.parent, recursive);
    } else {
      return null;
    }
  }

  /**
   * Should be called after modification of a time sheet assigned to the given task id.
   * @param taskId
   */
  public void resetTotalDuration(final Integer taskId)
  {
    final TaskNode node = getTaskNodeById(taskId);
    if (node == null) {
      log.error("Task id '" + taskId + "' not found.");
      return;
    }
    node.totalDuration = -1;
  }

  /**
   * After changing a task this method will be called by TaskDao for updating the task and the task tree.
   * @param task Updating the existing task in the taskTree. If not exist, a new task will be added.
   */
  TaskNode addOrUpdateTaskNode(final TaskDO task)
  {
    checkRefresh();
    Validate.notNull(task);
    Validate.notNull(task.getId());
    final TaskNode node = getTaskNodeById(task.getId());
    if (node == null) {
      return addTaskNode(task);
    }
    node.setTask(task);
    if (task.getParentTaskId() != null && task.getParentTaskId().equals(node.getParent().getId()) == false) {
      if (log.isDebugEnabled() == true) {
        log.debug("Task hierarchy was changed for task: " + task);
      }
      final TaskNode oldParent = node.getParent();
      Validate.notNull(oldParent);
      oldParent.removeChild(node);
      final TaskNode newParent = getTaskNodeById(task.getParentTaskId());
      node.setParent(newParent);
      newParent.addChild(node);
    }
    updateTimeOfLastModification();
    return node;
  }

  /**
   * Sets an explicit task group access for the given task (stored in the given groupTaskAccess). This method will be called by AccessDao
   * after inserting or updating GroupTaskAccess to the database.
   * @see GroupTaskAccess
   */
  public void setGroupTaskAccess(final GroupTaskAccessDO groupTaskAccess)
  {
    checkRefresh();
    final Integer taskId = groupTaskAccess.getTaskId();
    final TaskNode node = taskMap.get(taskId);
    node.setGroupTaskAccess(groupTaskAccess);
  }

  /**
   * Removes an explicit task group access for the given task (stored in the given groupTaskAccess). This method will be called by AccessDao
   * after deleting GroupTaskAccess from the database.
   * @see GroupTaskAccess
   */
  public void removeGroupTaskAccess(final GroupTaskAccessDO groupTaskAccess)
  {
    checkRefresh();
    final Integer taskId = groupTaskAccess.getTaskId();
    final TaskNode node = taskMap.get(taskId);
    node.removeGroupTaskAccess(groupTaskAccess.getGroupId());
  }

  public long getTimeOfLastModification()
  {
    return this.timeOfLastModification;
  }

  @Override
  public String toString()
  {
    if (root == null) {
      return "<empty/>";
    }
    final Document document = DocumentHelper.createDocument();
    final Element root = document.addElement("root");
    this.root.addXMLElement(root);
    // Pretty print the document to System.out
    final StringWriter sw = new StringWriter();
    String result = "";
    final XMLWriter writer = new XMLWriter(sw, OutputFormat.createPrettyPrint());
    try {
      writer.write(document);
      result = sw.toString();
    } catch (final IOException ex) {
      log.error(ex.getMessage(), ex);
    } finally {
      try {
        writer.close();
      } catch (final IOException ex) {
        log.error("Error while closing xml writer: " + ex.getMessage(), ex);
      }
    }
    return result;
  }

  public TaskTree()
  {
  }

  public void setTaskDao(final TaskDao taskDao)
  {
    this.taskDao = taskDao;
  }

  TaskDao getTaskDao()
  {
    return taskDao;
  }

  public void setGroupTaskAccessdao(final AccessDao accessDao)
  {
    this.accessDao = accessDao;
  }

  public void setProjektDao(final ProjektDao projektDao)
  {
    this.projektDao = projektDao;
  }

  public void setKostCache(final KostCache kostCache)
  {
    this.kostCache = kostCache;
  }

  public void setAuftragDao(final AuftragDao auftragDao)
  {
    this.auftragDao = auftragDao;
    auftragDao.registerTaskTree(this);
  }

  /**
   * Has the current logged in user select access to the given task?
   * @param node
   * @return
   */
  public boolean hasSelectAccess(final TaskNode node)
  {
    return taskDao.hasLoggedInUserSelectAccess(node.getTask(), false);
  }

  /**
   * @see #isRootNode(TaskDO)
   */
  public boolean isRootNode(final TaskNode node)
  {
    Validate.notNull(node);
    return isRootNode(node.getTask());
  }

  /**
   * @param node
   * @return true, if the given task has the same id as the task tree's root node, otherwise false;
   */
  public boolean isRootNode(final TaskDO task)
  {
    Validate.notNull(task);
    if (root == null && task.getParentTaskId() == null) {
      // First task, so it should be the root node.
      return true;
    }
    checkRefresh();
    if (task.getId() == null) {
      // Node has no id, so it can't be the root node.
      return false;
    }
    return root.getId().equals(task.getId());
  }

  /**
   * Should be called after manipulations of any order position if a task reference was changed. This method declares the reference map as
   * dirty, therefore before the next usage the map will be rebuild from the database.
   */
  public void refreshOrderPositionReferences()
  {
    synchronized (this) {
      this.orderPositionReferencesDirty = true;
    }
  }

  /**
   * Does any order position entry with a task reference exist?
   */
  public boolean hasOrderPositionsEntries()
  {
    checkRefresh();
    return (MapUtils.isNotEmpty(getOrderPositionEntries()));
  }

  private Map<Integer, Set<AuftragsPositionVO>> getOrderPositionEntries()
  {
    synchronized (this) {
      if (this.orderPositionReferencesDirty == true) {
        this.orderPositionReferences = auftragDao.getTaskReferences();
        if (this.orderPositionReferences != null) {
          resetOrderPersonDays(this.root);
          for (final Map.Entry<Integer, Set<AuftragsPositionVO>> entry : this.orderPositionReferences.entrySet()) {
            final TaskNode node = getTaskNodeById(entry.getKey());
            node.orderedPersonDays = null;
            if (CollectionUtils.isNotEmpty(entry.getValue()) == true) {
              for (final AuftragsPositionVO pos : entry.getValue()) {
                if (pos.getPersonDays() == null) {
                  continue;
                }
                if (node.orderedPersonDays == null) {
                  node.orderedPersonDays = BigDecimal.ZERO;
                }
                node.orderedPersonDays = node.orderedPersonDays.add(pos.getPersonDays());
              }
            }
          }
        }
        this.orderPositionReferencesDirty = false;
      }
      return this.orderPositionReferences;
    }
  }

  private void resetOrderPersonDays(final TaskNode node)
  {
    node.orderedPersonDays = null;
    if (node.hasChilds() == true) {
      for (final TaskNode child : node.getChilds()) {
        resetOrderPersonDays(child);
      }
    }
  }

  /**
   * @param taskId
   * @return Set of all order positions assigned to the given task.
   */
  public Set<AuftragsPositionVO> getOrderPositionEntries(final Integer taskId)
  {
    checkRefresh();
    return getOrderPositionEntries().get(taskId);
  }

  /**
   * @return Set of all order positions assigned to the given task and any of the ancestor tasks.
   * @param taskId
   * @return
   */
  public Set<AuftragsPositionVO> getOrderPositionsUpwards(final Integer taskId)
  {
    final Set<AuftragsPositionVO> set = new TreeSet<AuftragsPositionVO>();
    addOrderPositionsUpwards(set, taskId);
    return set;
  }

  private void addOrderPositionsUpwards(final Set<AuftragsPositionVO> set, final Integer taskId)
  {
    final Set<AuftragsPositionVO> set2 = getOrderPositionEntries(taskId);
    if (CollectionUtils.isNotEmpty(set2) == true) {
      set.addAll(set2);
    }
    final TaskDO task = getTaskById(taskId);
    if (task != null && task.getParentTaskId() != null) {
      addOrderPositionsUpwards(set, task.getParentTaskId());
    }
  }

  /**
   * @param taskId
   * @param recursive if true also all descendant tasks will be searched for assigned order positions.
   * @return
   */
  public boolean hasOrderPositions(final Integer taskId, final boolean recursive)
  {
    if (taskId == null) { // For new tasks.
      return false;
    }
    if (CollectionUtils.isNotEmpty(getOrderPositionEntries(taskId)) == true) {
      return true;
    }
    if (recursive == true) {
      final TaskNode node = getTaskNodeById(taskId);
      if (node != null && node.hasChilds() == true) {
        for (final TaskNode child : node.getChilds()) {
          if (hasOrderPositions(child.getId(), recursive) == true) {
            return true;
          }
        }
      }
    }
    return false;
  }

  /**
   * @param taskId
   * @return True, if the given task has order positions or any ancestor task has an order position.
   */
  public boolean hasOrderPositionsUpwards(final Integer taskId)
  {
    if (hasOrderPositions(taskId, false) == true) {
      return true;
    }
    final TaskNode task = getTaskNodeById(taskId);
    if (task != null && task.getParentId() != null) {
      return hasOrderPositionsUpwards(task.getParentId());
    }
    return false;
  }

  /**
   * @param taskId
   * @see #getPersonDays(TaskNode)
   */
  public BigDecimal getPersonDays(final Integer taskId)
  {
    final TaskNode node = getTaskNodeById(taskId);
    return getPersonDays(node);
  }

  /**
   * @param node
   * @return The ordered person days or if not found the defined max hours. If both not found, the get the sum of all diect or null if both
   *         not found.
   * @see #getOrderedPersonDays(TaskNode)
   * @see TaskNode#getMaxHours()
   */
  public BigDecimal getPersonDays(final TaskNode node)
  {
    checkRefresh();
    if (node == null || node.isDeleted() == true) {
      return null;
    }
    if (hasOrderPositions(node.getId(), true) == true) {
      return getOrderedPersonDaysSum(node);
    }
    final Integer maxHours = node.getTask().getMaxHours();
    if (maxHours != null) {
      return new BigDecimal(maxHours).divide(DateHelper.HOURS_PER_WORKING_DAY, 2, BigDecimal.ROUND_HALF_UP);
    }
    if (node.hasChilds() == false) {
      return null;
    }
    BigDecimal result = null;
    for (final TaskNode child : node.getChilds()) {
      final BigDecimal childPersonDays = getPersonDays(child);
      if (childPersonDays != null) {
        if (result == null) {
          result = BigDecimal.ZERO;
        }
        result = result.add(childPersonDays);
      }
    }
    return result;
  }

  /**
   * @return The sum of all ordered person days. This method checks the given node and all sub-nodes for assigned order positions.
   */
  public BigDecimal getOrderedPersonDaysSum(final TaskNode node)
  {
    BigDecimal personDays = null;
    if (node.orderedPersonDays != null) {
      personDays = node.orderedPersonDays;
    }
    if (node.hasChilds() == true) {
      for (final TaskNode child : node.getChilds()) {
        final BigDecimal childPersonDays = getOrderedPersonDaysSum(child);
        if (childPersonDays != null) {
          if (personDays == null) {
            personDays = childPersonDays;
          } else {
            personDays = personDays.add(childPersonDays);
          }
        }
      }
    }
    return personDays;
  }

  public TaskNode getPersonDaysNode(final TaskNode node)
  {
    if (node == null) {
      return null;
    }
    if (node.orderedPersonDays != null) {
      return node;
    }
    if (NumberHelper.greaterZero(node.getTask().getMaxHours()) == true) {
      return node;
    }
    return getPersonDaysNode(node.getParent());
  }

  /**
   * Reads the sum of all time sheet durations grouped by task id and set the total duration of found taskNodes.
   */
  private void readTotalDurations()
  {
    final List<Object[]> list = taskDao.readTotalDurations();
    for (final Object[] res : list) {
      final Integer taskId = (Integer) res[1];
      final TaskNode node = getTaskNodeById(taskId);
      if (node == null) {
        log.warn("Task not found: " + taskId);
      } else {
        if (res[0] instanceof Integer) {
          node.totalDuration = (Integer) res[0];
        } else {
          node.totalDuration = (Long) res[0];
        }
      }
    }
  }

  /**
   * Reads the sum of all time sheet durations grouped by task id and set the total duration of found taskNodes.
   */
  public void readTotalDuration(final Integer taskId)
  {
    final long duration = taskDao.readTotalDuration(taskId);
    final TaskNode node = getTaskNodeById(taskId);
    if (node == null) {
      log.warn("Task not found: " + taskId);
    } else {
      node.totalDuration = duration;
    }
  }

  /**
   * Should only called by test suite!
   */
  public void clear()
  {
    this.root = null;
    this.setExpired();
  }

  /**
   * All tasks from database will be read and cached into this TaskTree. Also all explicit group task access' will be read from database and
   * will be cached in this tree (implicit access' will be created too).<br/>
   * The generation of the task tree will be done manually, not by hibernate because the task hierarchy is very sensible. Manipulations of
   * the task tree should be done carefully for single task nodes.
   *
   * @see org.projectforge.common.AbstractCache#refresh()
   */
  @Override
  protected void refresh()
  {
    log.info("Initializing task tree ...");
    if (taskDao == null) {
      log.info("Can't initialize task tree, taskDao isn't set yet (shouldn't occur):");
      // Stack trace for debugging refresh() call without TaskDao (does only occur in productive mode):
      final StackTraceHolder sth = new StackTraceHolder();
      log.info(sth);
      return;
    }
    TaskNode newRoot = null;
    taskMap = new HashMap<Integer, TaskNode>();
    final List<TaskDO> taskList = taskDao.internalLoadAll();
    TaskNode node;
    log.debug("Loading list of tasks ...");
    for (final TaskDO task : taskList) {
      node = new TaskNode();
      node.setTask(task);
      taskMap.put(node.getTaskId(), node);
      if (node.isRootNode() == true) {
        if (newRoot != null) {
          log.error("Duplicate root node found: " + newRoot.getId() + " and " + node.getId());
          node.setParent(newRoot); // Set the second root task as child task of first read root task.
        } else {
          if (log.isDebugEnabled() == true) {
            log.debug("Root note found: " + node);
          }
          newRoot = node;
        }
      }
    }

    if (newRoot == null) {
      log.fatal("OUPS, no task found (ProjectForge database not initialized?) OK, initialize it ...");
      final TaskDO rootTask = new TaskDO();
      rootTask.setTitle("root");
      rootTask.setShortDescription("ProjectForge root task");
      taskDao.internalSave(rootTask);
      newRoot = new TaskNode();
      newRoot.setTask(rootTask);
      taskMap.put(newRoot.getTaskId(), newRoot);
    }
    this.root = newRoot;
    if (log.isDebugEnabled() == true) {
      log.debug("Creating tree for " + taskList.size() + " tasks ...");
    }
    for (final TaskDO task : taskList) {
      TaskNode parentNode = null;
      node = taskMap.get(task.getId());
      final Integer parentId = task.getParentTaskId();
      if (parentId != null) {
        parentNode = taskMap.get(parentId);
      }
      // log.debug("Processing node: " + node.getId() + ", parent: " + parentId);
      if (parentNode != null) {
        node.setParent(parentNode);
        parentNode.addChild(node);
        updateTimeOfLastModification();
      } else {
        log.debug("Processing root node:" + node);
      }
    }

    if (log.isDebugEnabled() == true) {
      log.debug(this.root);
    }

    // Now read all explicit group task access' from the database:
    final List<GroupTaskAccessDO> accessList = accessDao.internalLoadAll();
    for (final GroupTaskAccessDO access : accessList) {
      node = taskMap.get(access.getTaskId());
      node.setGroupTaskAccess(access);
      if (log.isDebugEnabled() == true) {
        log.debug(access.toString());
      }
    }
    // Now read all projects with their references to tasks:
    final List<ProjektDO> projects = projektDao.internalLoadAll();
    if (projects != null) {
      for (final ProjektDO project : projects) {
        if (project.isDeleted() == true || project.getTaskId() == null) {
          continue;
        }
        node = taskMap.get(project.getTaskId());
        if (node == null) {
          log.error("Oups, should not occur: project references a non existing task: " + project);
        } else {
          node.projekt = project;
        }
      }
    }
    if (log.isDebugEnabled() == true) {
      log.debug(this.toString());
    }
    readTotalDurations();
    refreshOrderPositionReferences();
    // Now update the status: bookable for time sheets:
    final TimesheetDao timesheetDao = Registry.instance().getDao(TimesheetDao.class);
    final TimesheetDO timesheet = new TimesheetDO();
    for (final TaskDO task : taskList) {
      node = taskMap.get(task.getId());
      timesheet.setTask(task);
      final boolean bookable = timesheetDao.checkTaskBookable(timesheet, null, OperationType.INSERT, false);
      node.bookableForTimesheets = bookable;
    }
    log.info("Initializing task tree done.");
  }

  private void updateTimeOfLastModification()
  {
    this.timeOfLastModification = new Date().getTime();
  }
}
TOP

Related Classes of org.projectforge.task.TaskTree

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.