/////////////////////////////////////////////////////////////////////////////
//
// 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.gantt;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.projectforge.common.BeanHelper;
import org.projectforge.common.NumberHelper;
import org.projectforge.core.BaseDao;
import org.projectforge.task.TaskDO;
import org.projectforge.task.TaskDao;
import org.projectforge.task.TaskTree;
import org.projectforge.user.PFUserContext;
import org.projectforge.user.PFUserDO;
import org.projectforge.user.UserDao;
import org.projectforge.user.UserRightId;
import org.projectforge.xml.stream.AliasMap;
import org.projectforge.xml.stream.ProjectForgeRootElement;
import org.projectforge.xml.stream.Status;
import org.projectforge.xml.stream.XmlConstants;
import org.projectforge.xml.stream.XmlField;
import org.projectforge.xml.stream.XmlHelper;
import org.projectforge.xml.stream.XmlObject;
import org.projectforge.xml.stream.XmlObjectReader;
import org.projectforge.xml.stream.XmlObjectWriter;
import org.projectforge.xml.stream.XmlRegistry;
import org.projectforge.xml.stream.converter.ISODateConverter;
/**
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public class GanttChartDao extends BaseDao<GanttChartDO>
{
public static final UserRightId USER_RIGHT_ID = UserRightId.PM_GANTT;
private static final String[] ADDITIONAL_SEARCH_FIELDS = new String[] { "task.title", "task.taskpath", "owner.username",
"owner.firstname", "owner.lastname"};
private TaskDao taskDao;
private UserDao userDao;
private Field[] taskFields = BeanHelper.getDeclaredPropertyFields(TaskDO.class);
/**
* Mapping of GanttTask fields to TaskDO fields.
*/
private Map<String, String> fieldMapping;
private AliasMap xmlGanttObjectAliasMap;
public GanttChartDao()
{
super(GanttChartDO.class);
userRightId = USER_RIGHT_ID;
AccessibleObject.setAccessible(taskFields, true);
fieldMapping = new HashMap<String, String>();
fieldMapping.put("predecessor", "ganttPredecessor");
fieldMapping.put("predecessorOffset", "ganttPredecessorOffset");
fieldMapping.put("relationType", "ganttRelationType");
fieldMapping.put("type", "ganttObjectType");
}
@Override
protected String[] getAdditionalSearchFields()
{
return ADDITIONAL_SEARCH_FIELDS;
}
@Override
protected void onSaveOrModify(final GanttChartDO obj)
{
final String styleAsXml = XmlObjectWriter.writeAsXml(obj.getStyle());
obj.setStyleAsXml(styleAsXml);
final String settingsAsXml = XmlObjectWriter.writeAsXml(obj.getSettings());
obj.setSettingsAsXml(settingsAsXml);
}
public String exportAsXml(final GanttChart ganttChart)
{
return exportAsXml(ganttChart, false);
}
@XmlObject(alias = "ProjectForge")
public class MyRootElement extends ProjectForgeRootElement
{
@SuppressWarnings("unused")
private GanttChart ganttChart;
}
public String exportAsXml(final GanttChart ganttChart, final boolean prettyFormat)
{
final Document document = DocumentHelper.createDocument();
final XmlObjectWriter writer = getXmlGanttObjectWriter();
final XmlRegistry xmlRegistry = new XmlRegistry();
xmlRegistry.registerConverter(Date.class, new ISODateConverter());
writer.setXmlRegistry(xmlRegistry);
final MyRootElement root = new MyRootElement();
root.ganttChart = ganttChart;
root.setCreated().setTimeZone(PFUserContext.getTimeZone()).setVersion("1.0");
final Element element = writer.write(document, root);
// Now, remove all elements with no information from the DOM:
final String xml;
if (removeUnnecessaryElements(element) == true) {
// Nothing to write (no further information in the GanttObject tree given).
xml = "";
} else {
xml = XmlHelper.toString(element, prettyFormat);
}
return XmlHelper.XML_HEADER + xml;
}
/**
* Writes all Gantt objects as tree as xml. Writes only those values which are different to the original values of the task with the same
* id.
* @param obj
* @param rootObject
*/
public void writeGanttObjects(final GanttChartDO obj, final GanttTask rootObject)
{
final Document document = DocumentHelper.createDocument();
final Element element = getXmlGanttObjectWriter().write(document, rootObject);
// Now, remove all elements with no information from the DOM:
final String xml;
if (removeUnnecessaryElements(element) == true) {
// Nothing to write (no further information in the GanttObject tree given).
xml = "";
} else {
xml = XmlHelper.toString(element);
}
obj.setGanttObjectsAsXml(xml);
}
/**
* Removes all unnecessary GanttObject elements from the DOM (those without any information rather than the id).
*/
private boolean removeUnnecessaryElements(final Element element)
{
if (CollectionUtils.isNotEmpty(element.elements()) == true) {
for (final Object childObj : element.elements()) {
final Element child = (Element) childObj;
if (removeUnnecessaryElements(child) == true) {
element.remove(child);
}
}
}
if (CollectionUtils.isNotEmpty(element.elements()) == true) {
// Element has descendants.
return false;
}
if (StringUtils.isBlank(element.getText()) == false) {
// Element has non blank content.
return false;
}
// Element has no descendants:
if (CollectionUtils.isEmpty(element.attributes()) == true) {
// Element has no attributes.
return true;
}
if ("predecessor".equals(element.getName()) == true) {
if (element.attribute(XmlObjectWriter.ATTR_ID) != null) {
// Describes a complete Gantt task which is referenced, so full output is needed.
return false;
} else {
final Attribute idAttr = element.attribute("id");
final Attribute refIdAttr = element.attribute(XmlObjectWriter.ATTR_REF_ID);
element.setAttributes(null);
if (refIdAttr != null) {
element.addAttribute(XmlObjectWriter.ATTR_REF_ID, refIdAttr.getValue());
} else if (idAttr != null) {
// External reference (no ref-id, but task id):
element.addAttribute("id", idAttr.getValue());
} else {
// Should not occur.
return true;
}
return false;
}
} else if (element.attributes().size() == 1 && element.attribute("id") != null) {
// Element has only id attribute and is not a predecessor definition for tasks outside the current Gantt object tree.
return true;
}
return false;
}
/**
* Reads all Gantt objects as tree from xml and TaskTree.
* @param obj
* @return The root object of the read xml data.
*/
public GanttChartData readGanttObjects(final GanttChartDO obj)
{
final TaskTree taskTree = taskDao.getTaskTree();
final GanttChartData ganttChartData = Task2GanttTaskConverter.convertToGanttObjectTree(taskTree, obj.getTask());
final XmlObjectReader reader = new XmlObjectReader() {
@Override
protected Object newInstance(final Class< ? > clazz, final Element el, final String attrName, final String attrValue)
{
if ("predecessor".equals(attrName) == true && XmlConstants.NULL_IDENTIFIER.equals(attrValue) == true) {
// Field should set to null.
return Status.IGNORE;
}
if (GanttTask.class.isAssignableFrom(clazz) == true) {
final GanttTask ganttObject = getGanttObject(taskTree, ganttChartData, el);
if (ganttObject == null) {
return new GanttTaskImpl(); // Gantt task not related to a ProjectForge task.
}
return ganttObject;
} else if (Collection.class.isAssignableFrom(clazz) == true) {
final GanttTask ganttObject = getGanttObject(taskTree, ganttChartData, el.getParent());
if (ganttObject != null && ganttObject.getChildren() != null) {
return ganttObject.getChildren();
}
}
return null;
}
@Override
protected boolean addCollectionEntry(final Collection< ? > col, final Object obj, final Element el)
{
if (obj instanceof GanttTask == false) {
return false;
}
final GanttTask ganttTask = (GanttTask)obj;
if (ganttChartData.findById(ganttTask.getId()) != null) {
// GanttTask already added to the Gantt object tree.
return true;
}
if (taskTree.getTaskById((Integer)ganttTask.getId()) != null) {
// External task, so ignore it:
return true;
}
return false;
}
@Override
protected void setField(final Field field, final Object obj, final Object value, final Element element, final String key,
final String attrValue)
{
if (XmlConstants.NULL_IDENTIFIER.equals(attrValue) == true) {
// Overwrite value from task with null.
setField(field, obj, null);
return;
}
super.setField(field, obj, value, element, key, attrValue);
}
};
reader.setAliasMap(getXmlGanttObjectAliasMap()).setIgnoreEmptyCollections(true);
reader.read(obj.getGanttObjectsAsXml()); // Ignore the return value. If the task tree has changed, the task tree of root rules.
return ganttChartData;
}
private GanttTask getGanttObject(final TaskTree taskTree, final GanttChartData ganttChartData, final Element el)
{
final String idString = el.attributeValue("id");
final Integer id = NumberHelper.parseInteger(idString);
GanttTask ganttObject = ganttChartData.findById(id);
if (ganttObject == null) {
ganttObject = ganttChartData.ensureAndGetExternalGanttObject(taskTree.getTaskById(id));
}
return ganttObject;
}
@Override
protected void afterLoad(final GanttChartDO obj)
{
final XmlObjectReader reader = new XmlObjectReader();
reader.initialize(GanttChartStyle.class);
reader.initialize(GanttChartSettings.class);
final String styleAsXml = obj.getStyleAsXml();
final GanttChartStyle style;
if (StringUtils.isEmpty(styleAsXml) == true) {
style = new GanttChartStyle();
} else {
style = (GanttChartStyle) reader.read(styleAsXml);
}
obj.setStyle(style);
final String settingsAsXml = obj.getSettingsAsXml();
final GanttChartSettings settings;
if (StringUtils.isEmpty(settingsAsXml) == true) {
settings = new GanttChartSettings();
} else {
settings = (GanttChartSettings) reader.read(settingsAsXml);
}
obj.setSettings(settings);
}
private AliasMap getXmlGanttObjectAliasMap()
{
if (this.xmlGanttObjectAliasMap == null) {
this.xmlGanttObjectAliasMap = new AliasMap();
this.xmlGanttObjectAliasMap.put(GanttTaskImpl.class, "ganttObject");
}
return this.xmlGanttObjectAliasMap;
}
/**
* Ignores all field values in output which are equal to the values of the corresponding task.
*/
private XmlObjectWriter getXmlGanttObjectWriter()
{
final XmlObjectWriter xmlGanttObjectWriter = new XmlObjectWriter() {
@Override
protected boolean ignoreField(final Object obj, final Field field)
{
if (super.ignoreField(obj, field) == true) {
return true;
}
if (obj instanceof GanttTask) {
final TaskTree taskTree = taskDao.getTaskTree();
final String fieldName = field.getName();
if ("id".equals(fieldName) == true) {
// Id should always be equals and needed in output for the identification of the gantt object.
return false;
}
if ("description".equals(fieldName) == true) {
return true;
}
final GanttTask ganttObject = (GanttTask) obj;
final TaskDO task = taskTree.getTaskById((Integer) ganttObject.getId());
if (task != null) {
if ("predecessor".equals(field.getName()) == true) {
// Predecessor unmodified?
return NumberHelper.isEqual((Integer) ganttObject.getPredecessorId(), task.getGanttPredecessorId());
}
String taskFieldname = fieldMapping.get(fieldName);
if (taskFieldname == null) {
taskFieldname = fieldName;
}
for (final Field taskField : taskFields) {
if (taskFieldname.equals(taskField.getName()) == true) {
final Object value = BeanHelper.getFieldValue(obj, field);
final Object taskValue = BeanHelper.getFieldValue(task, taskField);
if (value instanceof BigDecimal) {
// Needed, because 10.0 is not equal to 10.000 (if scale is different).
return NumberHelper.isEqual((BigDecimal) value, (BigDecimal) taskValue);
}
return ObjectUtils.equals(value, taskValue) == true;
}
}
}
}
return false;
}
@Override
protected void writeField(final Field field, final Object obj, final Object fieldValue, final XmlField annotation,
final Element element)
{
if (GanttTask.class.isAssignableFrom(field.getDeclaringClass()) == true) {
final String fieldName = field.getName();
if ("id".equals(fieldName) == false) {
final TaskTree taskTree = taskDao.getTaskTree();
final GanttTask ganttObject = (GanttTask) obj;
final TaskDO task = taskTree.getTaskById((Integer) ganttObject.getId());
if (task != null) {
String taskFieldname = fieldMapping.get(fieldName);
if (taskFieldname == null) {
taskFieldname = fieldName;
}
for (final Field taskField : taskFields) {
if (taskFieldname.equals(taskField.getName()) == true) {
final Object value = BeanHelper.getFieldValue(obj, field);
final Object taskValue = BeanHelper.getFieldValue(task, taskField);
if (taskValue != null && value == null) {
// Reader should interpret this as null, so value from task will be overwritten by null.
element.addAttribute(field.getName(), XmlConstants.NULL_IDENTIFIER);
return;
}
}
}
}
}
}
super.writeField(field, obj, fieldValue, annotation, element);
}
};
xmlGanttObjectWriter.setAliasMap(getXmlGanttObjectAliasMap());
return xmlGanttObjectWriter;
}
/**
* @param ganttChart
* @param taskId If null, then task will be set to null;
* @see TaskTree#getTaskById(Integer)
*/
public void setTask(final GanttChartDO ganttChart, final Integer taskId)
{
final TaskDO task = taskDao.getOrLoad(taskId);
ganttChart.setTask(task);
}
/**
* @param sheet
* @param userId If null, then task will be set to null;
* @see BaseDao#getOrLoad(Integer)
*/
public void setOwner(final GanttChartDO ganttChart, final Integer userId)
{
final PFUserDO user = userDao.getOrLoad(userId);
ganttChart.setOwner(user);
}
@Override
public GanttChartDO newInstance()
{
return new GanttChartDO().setSettings(new GanttChartSettings()).setStyle(new GanttChartStyle());
}
public void setTaskDao(TaskDao taskDao)
{
this.taskDao = taskDao;
}
public void setUserDao(final UserDao userDao)
{
this.userDao = userDao;
}
}