From 6a32032d6581b8a2719e337882968f6351fe8960 Mon Sep 17 00:00:00 2001 From: Kai Reinhard Date: Thu, 23 Jan 2025 22:30:28 +0100 Subject: [PATCH 1/4] Hibernate.isInitialized(obj) -> HibernateUtils.isFullyInitialized(obj). Some lazy objects of e.g. ManyToOne columns are not fully loaded but Hibernate.isInitialized() returns true. --- .../projectforge/business/task/TaskTree.kt | 7 ++-- .../business/address/AddressbookCache.kt | 5 ++- .../business/fibu/EmployeeCache.kt | 4 +-- .../projectforge/business/fibu/KontoCache.kt | 4 +-- .../business/fibu/ProjektFormatter.kt | 7 ++-- .../business/fibu/kost/KostCache.kt | 8 ++--- .../business/fibu/kost/KundeCache.kt | 4 +-- .../business/fibu/kost/ProjektCache.kt | 7 ++-- .../business/timesheet/TimesheetDao.kt | 6 ++-- .../business/user/UserGroupCache.kt | 7 ++-- .../framework/access/AccessDao.kt | 5 +-- .../framework/access/GroupTaskAccessDO.kt | 4 +-- .../framework/json/IdsOnlySerializer.kt | 4 +-- .../persistence/api/HibernateUtils.kt | 16 +++++++++ .../persistence/candh/CollectionHandler.kt | 4 +-- .../persistence/user/entities/GroupDO.kt | 34 ++++++++++++------- 16 files changed, 76 insertions(+), 50 deletions(-) diff --git a/projectforge-business/src/main/java/org/projectforge/business/task/TaskTree.kt b/projectforge-business/src/main/java/org/projectforge/business/task/TaskTree.kt index 9b8ca690bc..d6a14c768e 100644 --- a/projectforge-business/src/main/java/org/projectforge/business/task/TaskTree.kt +++ b/projectforge-business/src/main/java/org/projectforge/business/task/TaskTree.kt @@ -42,6 +42,7 @@ import org.projectforge.framework.access.GroupTaskAccessDO import org.projectforge.framework.access.OperationType import org.projectforge.framework.cache.AbstractCache import org.projectforge.framework.i18n.InternalErrorException +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.projectforge.framework.time.DateHelper import org.projectforge.framework.utils.NumberHelper.greaterZero @@ -273,7 +274,7 @@ class TaskTree : AbstractCache(TICKS_PER_HOUR), */ fun getTaskIfNotInitialized(task: TaskDO?): TaskDO? { task ?: return null - if (Hibernate.isInitialized(task)) { + if (HibernateUtils.isFullyInitialized(task)) { return task } return getTaskById(task.id) @@ -570,7 +571,7 @@ class TaskTree : AbstractCache(TICKS_PER_HOUR), get() { synchronized(this) { if (this.orderPositionReferencesDirty) { - log.info{"TaskTree: refreshing order position references..."} + log.info { "TaskTree: refreshing order position references..." } val duration = LogDuration() val references = mutableMapOf>() persistenceService.runIsolatedReadOnly { context -> @@ -600,7 +601,7 @@ class TaskTree : AbstractCache(TICKS_PER_HOUR), } this.orderPositionReferences = references this.orderPositionReferencesDirty = false - log.info{"TaskTree: refreshing order position references done: $duration"} + log.info { "TaskTree: refreshing order position references done: $duration" } } return this.orderPositionReferences } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/address/AddressbookCache.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/address/AddressbookCache.kt index bdfd7791e6..bc79752692 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/address/AddressbookCache.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/address/AddressbookCache.kt @@ -26,11 +26,10 @@ package org.projectforge.business.address import jakarta.annotation.PostConstruct import jakarta.persistence.Tuple import mu.KotlinLogging -import org.hibernate.Hibernate -import org.projectforge.business.fibu.kost.Kost1DO import org.projectforge.framework.access.OperationType import org.projectforge.framework.cache.AbstractCache import org.projectforge.framework.persistence.api.BaseDOModifiedListener +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.database.TupleUtils.getLong import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.springframework.beans.factory.annotation.Autowired @@ -76,7 +75,7 @@ open class AddressbookCache : AbstractCache() { */ fun getAddressbookIfNotInitialized(ab: AddressbookDO?): AddressbookDO? { ab ?: return null - if (Hibernate.isInitialized(ab)) { + if (HibernateUtils.isFullyInitialized(ab)) { return ab } return getAddressbook(ab.id) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/EmployeeCache.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/EmployeeCache.kt index 3deb09560e..0b4f46d505 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/EmployeeCache.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/EmployeeCache.kt @@ -25,9 +25,9 @@ package org.projectforge.business.fibu import jakarta.annotation.PostConstruct import mu.KotlinLogging -import org.hibernate.Hibernate import org.projectforge.business.user.UserGroupCache import org.projectforge.framework.cache.AbstractCache +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.projectforge.framework.persistence.user.entities.PFUserDO import org.springframework.beans.factory.annotation.Autowired @@ -70,7 +70,7 @@ open class EmployeeCache : AbstractCache() { */ fun getEmployeeIfNotInitialized(employee: EmployeeDO?): EmployeeDO? { employee ?: return null - if (Hibernate.isInitialized(employee) && employee.user != null) { + if (HibernateUtils.isFullyInitialized(employee) && employee.user != null) { return employee } return getEmployee(employee.id) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/KontoCache.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/KontoCache.kt index 241becbc8e..ab1229c65b 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/KontoCache.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/KontoCache.kt @@ -25,10 +25,10 @@ package org.projectforge.business.fibu import mu.KotlinLogging import org.apache.commons.collections4.MapUtils -import org.hibernate.Hibernate import org.projectforge.business.fibu.kost.KundeCache import org.projectforge.business.fibu.kost.ProjektCache import org.projectforge.framework.cache.AbstractCache +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -101,7 +101,7 @@ open class KontoCache : AbstractCache() { */ fun getKontoIfNotInitialized(konto: KontoDO?): KontoDO? { konto ?: return null - if (Hibernate.isInitialized(konto)) { + if (HibernateUtils.isFullyInitialized(konto)) { return konto } return getKonto(konto.id) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/ProjektFormatter.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/ProjektFormatter.kt index c3aabc16f9..aeb4121b1a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/ProjektFormatter.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/ProjektFormatter.kt @@ -24,13 +24,12 @@ package org.projectforge.business.fibu import jakarta.annotation.PostConstruct -import org.hibernate.Hibernate import org.projectforge.business.fibu.kost.KundeCache import org.projectforge.business.fibu.kost.ProjektCache import org.projectforge.business.utils.BaseFormatter +import org.projectforge.framework.persistence.api.HibernateUtils import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service -import java.lang.StringBuilder @Service class ProjektFormatter : BaseFormatter() { @@ -60,7 +59,7 @@ class ProjektFormatter : BaseFormatter() { */ fun format(projekt: ProjektDO?, showOnlyNumber: Boolean): String? { var useProjekt = projekt - if (projekt?.id != null && !Hibernate.isInitialized(projekt)) { + if (projekt?.id != null && !HibernateUtils.isFullyInitialized(projekt)) { useProjekt = projektCache.getProjekt(projekt.id) } if (useProjekt == null) { @@ -94,7 +93,7 @@ class ProjektFormatter : BaseFormatter() { @JvmOverloads fun formatProjektKundeAsString(projekt: ProjektDO?, kunde: KundeDO? = null, kundeText: String? = null): String { val useProjekt = instance.projektCache.getProjektIfNotInitialized(projekt) - val projektKunde = instance.kundeCache.getKundeIfNotInitialized(useProjekt?.kunde) + val projektKunde = instance.kundeCache.getKundeIfNotInitialized(useProjekt?.kunde) val useKunde = instance.kundeCache.getKundeIfNotInitialized(kunde) return formatProjektKundeAsStringWithoutCache(useProjekt, projektKunde, useKunde, kundeText) } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KostCache.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KostCache.kt index 1a43544671..512d77e5aa 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KostCache.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KostCache.kt @@ -26,13 +26,13 @@ package org.projectforge.business.fibu.kost import jakarta.annotation.PostConstruct import jakarta.persistence.LockModeType import mu.KotlinLogging -import org.hibernate.Hibernate import org.projectforge.business.fibu.KundeDO import org.projectforge.business.fibu.KundeDao import org.projectforge.business.fibu.kost.KostHelper.parseKostString import org.projectforge.framework.access.OperationType import org.projectforge.framework.cache.AbstractCache import org.projectforge.framework.persistence.api.BaseDOModifiedListener +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.projectforge.framework.utils.NumberHelper.greaterZero import org.projectforge.reporting.Kost2Art @@ -101,7 +101,7 @@ class KostCache : AbstractCache() { */ fun getKost2IfNotInitialized(kost2: Kost2DO?): Kost2DO? { kost2 ?: return null - if (Hibernate.isInitialized(kost2)) { + if (HibernateUtils.isFullyInitialized(kost2)) { return kost2 } return getKost2(kost2.id) @@ -152,7 +152,7 @@ class KostCache : AbstractCache() { */ fun getKost1IfNotInitialized(kost1: Kost1DO?): Kost1DO? { kost1 ?: return null - if (Hibernate.isInitialized(kost1)) { + if (HibernateUtils.isFullyInitialized(kost1)) { return kost1 } return getKost1(kost1.id) @@ -187,7 +187,7 @@ class KostCache : AbstractCache() { */ fun getKost2ArtIfNotInitialized(kost2Art: Kost2ArtDO?): Kost2ArtDO? { kost2Art ?: return null - if (Hibernate.isInitialized(kost2Art)) { + if (HibernateUtils.isFullyInitialized(kost2Art)) { return kost2Art } return getKost2Art(kost2Art.id) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KundeCache.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KundeCache.kt index eb17b48f4a..2f41ac84b1 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KundeCache.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/KundeCache.kt @@ -26,12 +26,12 @@ package org.projectforge.business.fibu.kost import jakarta.annotation.PostConstruct import jakarta.persistence.LockModeType import mu.KotlinLogging -import org.hibernate.Hibernate import org.projectforge.business.fibu.KundeDO import org.projectforge.business.fibu.KundeDao import org.projectforge.framework.access.OperationType import org.projectforge.framework.cache.AbstractCache import org.projectforge.framework.persistence.api.BaseDOModifiedListener +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -84,7 +84,7 @@ class KundeCache : AbstractCache() { */ fun getKundeIfNotInitialized(kunde: KundeDO?): KundeDO? { kunde ?: return null - if (Hibernate.isInitialized(kunde)) { + if (HibernateUtils.isFullyInitialized(kunde)) { return kunde } return getKunde(kunde.nummer) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/ProjektCache.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/ProjektCache.kt index 61d5ae1e49..653929befa 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/ProjektCache.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/fibu/kost/ProjektCache.kt @@ -26,11 +26,12 @@ package org.projectforge.business.fibu.kost import jakarta.annotation.PostConstruct import jakarta.persistence.LockModeType import mu.KotlinLogging -import org.hibernate.Hibernate -import org.projectforge.business.fibu.* +import org.projectforge.business.fibu.ProjektDO +import org.projectforge.business.fibu.ProjektDao import org.projectforge.framework.access.OperationType import org.projectforge.framework.cache.AbstractCache import org.projectforge.framework.persistence.api.BaseDOModifiedListener +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Component @@ -79,7 +80,7 @@ class ProjektCache : AbstractCache() { */ fun getProjektIfNotInitialized(projekt: ProjektDO?): ProjektDO? { projekt ?: return null - if (Hibernate.isInitialized(projekt)) { + if (HibernateUtils.isFullyInitialized(projekt)) { return projekt } return getProjekt(projekt.id) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/timesheet/TimesheetDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/timesheet/TimesheetDao.kt index 10d0e990a1..c0d5e59d7b 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/timesheet/TimesheetDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/timesheet/TimesheetDao.kt @@ -28,7 +28,6 @@ import mu.KotlinLogging import org.apache.commons.collections4.CollectionUtils import org.apache.commons.lang3.Validate import org.apache.commons.lang3.builder.ToStringBuilder -import org.hibernate.Hibernate import org.projectforge.business.common.AutoCompletionUtils import org.projectforge.business.fibu.kost.Kost2DO import org.projectforge.business.fibu.kost.Kost2Dao @@ -47,6 +46,7 @@ import org.projectforge.framework.configuration.Configuration import org.projectforge.framework.configuration.ConfigurationParam import org.projectforge.framework.persistence.api.BaseDao import org.projectforge.framework.persistence.api.BaseSearchFilter +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.api.QueryFilter import org.projectforge.framework.persistence.api.QueryFilter.Companion.and import org.projectforge.framework.persistence.api.QueryFilter.Companion.eq @@ -331,11 +331,11 @@ open class TimesheetDao : BaseDao(TimesheetDO::class.java) { override fun prepareHibernateSearch(obj: TimesheetDO, operationType: OperationType) { val user = obj.user - if (user != null && !Hibernate.isInitialized(user)) { + if (user != null && !HibernateUtils.isFullyInitialized(user)) { obj.user = userGroupCache.getUser(user.id) } val task = obj.task - if (task != null && !Hibernate.isInitialized(task)) { + if (task != null && !HibernateUtils.isFullyInitialized(task)) { obj.task = taskTree.getTaskById(task.id) } } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/business/user/UserGroupCache.kt b/projectforge-business/src/main/kotlin/org/projectforge/business/user/UserGroupCache.kt index b204c6e411..95871d0c6e 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/business/user/UserGroupCache.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/business/user/UserGroupCache.kt @@ -25,13 +25,12 @@ package org.projectforge.business.user import jakarta.annotation.PostConstruct import mu.KotlinLogging -import org.hibernate.Hibernate import org.projectforge.business.fibu.ProjektDO import org.projectforge.business.login.Login -import org.projectforge.business.task.TaskDO import org.projectforge.framework.ToStringUtil import org.projectforge.framework.cache.AbstractCache import org.projectforge.framework.jobs.JobHandler +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.api.UserRightService import org.projectforge.framework.persistence.jpa.PfPersistenceService import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext @@ -116,7 +115,7 @@ open class UserGroupCache : AbstractCache() { */ fun getGroupIfNotInitialized(group: GroupDO?): GroupDO? { group ?: return null - if (Hibernate.isInitialized(group)) { + if (HibernateUtils.isFullyInitialized(group)) { return group } return getGroup(group.id) @@ -161,7 +160,7 @@ open class UserGroupCache : AbstractCache() { */ fun getUserIfNotInitialized(user: PFUserDO?): PFUserDO? { user ?: return null - if (Hibernate.isInitialized(user)) { + if (HibernateUtils.isFullyInitialized(user)) { return user } return getUser(user.id) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/framework/access/AccessDao.kt b/projectforge-business/src/main/kotlin/org/projectforge/framework/access/AccessDao.kt index 6db44e6bc6..b4948618c3 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/framework/access/AccessDao.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/framework/access/AccessDao.kt @@ -33,6 +33,7 @@ import org.projectforge.business.task.TaskTree import org.projectforge.business.user.GroupDao import org.projectforge.framework.persistence.api.BaseDao import org.projectforge.framework.persistence.api.BaseSearchFilter +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.api.QueryFilter import org.projectforge.framework.persistence.api.QueryFilter.Companion.eq import org.projectforge.framework.persistence.api.QueryFilter.Companion.isIn @@ -294,12 +295,12 @@ open class AccessDao : BaseDao(GroupTaskAccessDO::class.java) operationType: OperationType, ) { val task = obj.task - if (task != null && !Hibernate.isInitialized(task)) { + if (task != null && !HibernateUtils.isFullyInitialized(task)) { Hibernate.initialize(obj.task) obj.task = taskTree.getTaskById(task.id) } val group = obj.group - if (group != null && !Hibernate.isInitialized(group)) { + if (group != null && !HibernateUtils.isFullyInitialized(group)) { obj.group = groupDao.findOrLoad(obj.groupId!!) } } diff --git a/projectforge-business/src/main/kotlin/org/projectforge/framework/access/GroupTaskAccessDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/framework/access/GroupTaskAccessDO.kt index 2f00035c81..d09012566d 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/framework/access/GroupTaskAccessDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/framework/access/GroupTaskAccessDO.kt @@ -27,7 +27,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize import jakarta.persistence.* import org.apache.commons.lang3.builder.HashCodeBuilder import org.apache.commons.lang3.builder.ToStringBuilder -import org.hibernate.Hibernate import org.hibernate.search.mapper.pojo.automaticindexing.ReindexOnUpdate import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed @@ -38,6 +37,7 @@ import org.projectforge.common.anots.PropertyInfo import org.projectforge.framework.json.IdOnlySerializer import org.projectforge.framework.persistence.api.BaseDO import org.projectforge.framework.persistence.api.EntityCopyStatus +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.entities.DefaultBaseDO import org.projectforge.framework.persistence.history.PersistenceBehavior import org.projectforge.framework.persistence.user.entities.GroupDO @@ -233,7 +233,7 @@ open class GroupTaskAccessDO : DefaultBaseDO() { tos.append("id", id) tos.append("task", taskId) tos.append("group", groupId) - if (Hibernate.isInitialized(this.accessEntries)) { + if (HibernateUtils.isFullyInitialized(this.accessEntries)) { tos.append("entries", this.accessEntries) } else { tos.append("entries", "LazyCollection") diff --git a/projectforge-business/src/main/kotlin/org/projectforge/framework/json/IdsOnlySerializer.kt b/projectforge-business/src/main/kotlin/org/projectforge/framework/json/IdsOnlySerializer.kt index 5d1a918d7d..2d8fe00dcc 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/framework/json/IdsOnlySerializer.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/framework/json/IdsOnlySerializer.kt @@ -26,7 +26,7 @@ package org.projectforge.framework.json import com.fasterxml.jackson.core.JsonGenerator import com.fasterxml.jackson.databind.JsonSerializer import com.fasterxml.jackson.databind.SerializerProvider -import org.hibernate.Hibernate +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.api.IdObject import java.io.IOException @@ -35,7 +35,7 @@ class IdsOnlySerializer : JsonSerializer>() { override fun serialize(value: Collection<*>?, gen: JsonGenerator, serializers: SerializerProvider) { if (value == null) { gen.writeNull() - } else if (Hibernate.isInitialized(value)) { + } else if (HibernateUtils.isFullyInitialized(value)) { gen.writeStartArray() value.forEach { item -> if (item is IdObject<*>) { diff --git a/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/api/HibernateUtils.kt b/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/api/HibernateUtils.kt index 49e34001f1..219da2322f 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/api/HibernateUtils.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/api/HibernateUtils.kt @@ -110,6 +110,22 @@ object HibernateUtils { HibernateMetaModel.internalInit(sessionFactoryImplementor) } + /** + * Checks if the given object is loaded. If not, the object is not fully initialized and can't be used. + * Uses Hibernate.isInitialized(obj) and Hibernate.isInitialized(obj.hibernateLazyInitializer.implementation) for lazy proxies. + * @param obj + */ + fun isFullyInitialized(obj: Any?): Boolean { + obj ?: return false + if (!Hibernate.isInitialized(obj)) { + return false + } + // Lazy-Proxy? + if (obj is HibernateProxy) { + return Hibernate.isInitialized(obj.hibernateLazyInitializer.implementation) + } + return true + } fun isEntity(entity: Class<*>): Boolean { return HibernateMetaModel.isEntity(entity) diff --git a/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/candh/CollectionHandler.kt b/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/candh/CollectionHandler.kt index ca8eba0ec4..560216aede 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/candh/CollectionHandler.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/candh/CollectionHandler.kt @@ -27,13 +27,13 @@ import jakarta.persistence.JoinColumn import jakarta.persistence.JoinTable import jakarta.persistence.OneToMany import mu.KotlinLogging -import org.hibernate.Hibernate import org.hibernate.collection.spi.PersistentList import org.hibernate.collection.spi.PersistentSet import org.projectforge.common.AnnotationsUtils import org.projectforge.common.KClassUtils import org.projectforge.framework.persistence.api.BaseDO import org.projectforge.framework.persistence.api.EntityCopyStatus +import org.projectforge.framework.persistence.api.HibernateUtils import org.projectforge.framework.persistence.candh.CandHMaster.copyValues import org.projectforge.framework.persistence.candh.CandHMaster.propertyWasModified import org.projectforge.framework.persistence.history.* @@ -289,7 +289,7 @@ open class CollectionHandler : CandHIHandler { // No, we have to handle the child collections of all existing collection entries: // Example: RechnungDO -> list of RechnungPositionDO -> list of KostZuweisungDO. mergedCol.forEach { mergedEntry -> - if (Hibernate.isInitialized(mergedEntry) && mergedEntry.id != null) { + if (HibernateUtils.isFullyInitialized(mergedEntry) && mergedEntry.id != null) { log.debug { "writeInsertHistoryEntriesForNewCollectionEntries: Check for history entries of child collections of (if any): ${mergedEntry::class.simpleName}:${mergedEntry.id}" } // Historize only existing entries. // If an entry is new, it's clear, that any existing child entry is also new. diff --git a/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/user/entities/GroupDO.kt b/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/user/entities/GroupDO.kt index a402c26be1..4ea1dcd51a 100644 --- a/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/user/entities/GroupDO.kt +++ b/projectforge-business/src/main/kotlin/org/projectforge/framework/persistence/user/entities/GroupDO.kt @@ -24,22 +24,22 @@ package org.projectforge.framework.persistence.user.entities import com.fasterxml.jackson.databind.annotation.JsonSerialize -import org.apache.commons.lang3.builder.HashCodeBuilder -import org.hibernate.Hibernate -import org.projectforge.common.StringHelper -import org.projectforge.common.anots.PropertyInfo -import org.projectforge.framework.DisplayNameCapable -import org.projectforge.framework.persistence.api.AUserRightId -import org.projectforge.framework.persistence.entities.DefaultBaseDO -import java.util.* import jakarta.persistence.* +import org.apache.commons.lang3.builder.HashCodeBuilder import org.hibernate.search.mapper.pojo.automaticindexing.ReindexOnUpdate import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexedEmbedded import org.hibernate.search.mapper.pojo.mapping.definition.annotation.IndexingDependency +import org.projectforge.common.StringHelper +import org.projectforge.common.anots.PropertyInfo +import org.projectforge.framework.DisplayNameCapable import org.projectforge.framework.json.IdOnlySerializer import org.projectforge.framework.json.IdsOnlySerializer +import org.projectforge.framework.persistence.api.AUserRightId +import org.projectforge.framework.persistence.api.HibernateUtils +import org.projectforge.framework.persistence.entities.DefaultBaseDO +import java.util.* /** * @author Kai Reinhard (k.reinhard@micromata.de) @@ -49,8 +49,9 @@ import org.projectforge.framework.json.IdsOnlySerializer @Table(name = "T_GROUP", uniqueConstraints = [UniqueConstraint(columnNames = ["name"])]) @AUserRightId("ADMIN_CORE") @NamedQueries( - NamedQuery(name = GroupDO.FIND_BY_NAME, query = "from GroupDO where name=:name"), - NamedQuery(name = GroupDO.FIND_OTHER_GROUP_BY_NAME, query = "from GroupDO where name=:name and id<>:id")) + NamedQuery(name = GroupDO.FIND_BY_NAME, query = "from GroupDO where name=:name"), + NamedQuery(name = GroupDO.FIND_OTHER_GROUP_BY_NAME, query = "from GroupDO where name=:name and id<>:id") +) open class GroupDO : DefaultBaseDO(), DisplayNameCapable { override val displayName: String @@ -133,7 +134,15 @@ open class GroupDO : DefaultBaseDO(), DisplayNameCapable { @IndexedEmbedded(includeDepth = 1) @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW) @get:ManyToMany(targetEntity = PFUserDO::class, fetch = FetchType.LAZY) - @get:JoinTable(name = "T_GROUP_USER", joinColumns = [JoinColumn(name = "GROUP_ID")], inverseJoinColumns = [JoinColumn(name = "USER_ID")], indexes = [jakarta.persistence.Index(name = "idx_fk_t_group_user_group_id", columnList = "group_id"), jakarta.persistence.Index(name = "idx_fk_t_group_user_user_id", columnList = "user_id")]) + @get:JoinTable( + name = "T_GROUP_USER", + joinColumns = [JoinColumn(name = "GROUP_ID")], + inverseJoinColumns = [JoinColumn(name = "USER_ID")], + indexes = [jakarta.persistence.Index( + name = "idx_fk_t_group_user_group_id", + columnList = "group_id" + ), jakarta.persistence.Index(name = "idx_fk_t_group_user_user_id", columnList = "user_id")] + ) @JsonSerialize(using = IdsOnlySerializer::class) open var assignedUsers: MutableSet? = null @@ -152,7 +161,7 @@ open class GroupDO : DefaultBaseDO(), DisplayNameCapable { */ val safeAssignedUsers: Set? @Transient - get() = if (this.assignedUsers == null || !Hibernate.isInitialized(this.assignedUsers)) { + get() = if (this.assignedUsers == null || !HibernateUtils.isFullyInitialized(this.assignedUsers)) { null } else this.assignedUsers @@ -205,6 +214,7 @@ open class GroupDO : DefaultBaseDO(), DisplayNameCapable { } internal const val FIND_BY_NAME = "GroupDO_FindByName" + /** * For detecting other groups with same groupname. */ From b1742cefdb88b9fd401b51009387280af816a5a4 Mon Sep 17 00:00:00 2001 From: Kai Reinhard Date: Thu, 23 Jan 2025 22:43:06 +0100 Subject: [PATCH 2/4] SkillMatrix: field owner fixed in list an edit page. --- .../plugins/skillmatrix/SkillEntry.kt | 35 +++ .../skillmatrix/SkillEntryPagesRest.kt | 241 ++++++++++-------- 2 files changed, 173 insertions(+), 103 deletions(-) create mode 100644 plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntry.kt diff --git a/plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntry.kt b/plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntry.kt new file mode 100644 index 0000000000..6420f2fc05 --- /dev/null +++ b/plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntry.kt @@ -0,0 +1,35 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Project ProjectForge Community Edition +// www.projectforge.org +// +// Copyright (C) 2001-2025 Micromata GmbH, Germany (www.micromata.com) +// +// 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.rest.dto + +import org.projectforge.plugins.skillmatrix.SkillEntryDO + +class SkillEntry( + id: Long? = null, + var skill: String? = null, + var owner: User? = null, + var rating: Int? = null, + var interest: Int? = null, + var comment: String? = null, +) : BaseDTODisplayObject(id = id) diff --git a/plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntryPagesRest.kt b/plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntryPagesRest.kt index 1b33f262a0..0b6c1d2859 100644 --- a/plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntryPagesRest.kt +++ b/plugins/org.projectforge.plugins.skillmatrix/src/main/kotlin/org/projectforge/plugins/skillmatrix/SkillEntryPagesRest.kt @@ -23,6 +23,9 @@ package org.projectforge.plugins.skillmatrix +import jakarta.servlet.http.HttpServletRequest +import org.projectforge.business.PfCaches +import org.projectforge.business.fibu.EmployeeDO import org.projectforge.framework.i18n.translate import org.projectforge.framework.persistence.api.MagicFilter import org.projectforge.framework.persistence.api.QueryFilter @@ -31,123 +34,155 @@ import org.projectforge.framework.persistence.user.api.ThreadLocalUserContext import org.projectforge.menu.MenuItem import org.projectforge.menu.MenuItemTargetType import org.projectforge.rest.config.Rest -import org.projectforge.rest.core.AbstractDOPagesRest +import org.projectforge.rest.core.AbstractDTOPagesRest +import org.projectforge.rest.dto.Employee +import org.projectforge.rest.dto.Kost1 +import org.projectforge.rest.dto.SkillEntry +import org.projectforge.rest.dto.User import org.projectforge.ui.* import org.projectforge.ui.filter.UIFilterElement +import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import jakarta.servlet.http.HttpServletRequest @RestController @RequestMapping("${Rest.URL}/skillentry") -class SkillEntryPagesRest() : AbstractDOPagesRest( - SkillEntryDao::class.java, - "plugins.skillmatrix.title", - cloneSupport = CloneSupport.CLONE +class SkillEntryPagesRest() : AbstractDTOPagesRest( + SkillEntryDao::class.java, + "plugins.skillmatrix.title", + cloneSupport = CloneSupport.CLONE ) { - /** - * Initializes new memos for adding. - */ - override fun newBaseDO(request: HttpServletRequest?): SkillEntryDO { - val memo = super.newBaseDO(request) - memo.owner = ThreadLocalUserContext.loggedInUser - return memo - } + @Autowired + private lateinit var caches: PfCaches + + /** + * Initializes new memos for adding. + */ + override fun newBaseDO(request: HttpServletRequest?): SkillEntryDO { + val memo = super.newBaseDO(request) + memo.owner = ThreadLocalUserContext.loggedInUser + return memo + } + + override fun transformFromDB(obj: SkillEntryDO, editMode: Boolean): SkillEntry { + val entry = SkillEntry() + entry.copyFrom(obj) + caches.getUser(obj.owner?.id)?.let { userDO -> + entry.owner = User(userDO) + } + return entry + } + + override fun transformForDB(dto: SkillEntry): SkillEntryDO { + val entryDO = SkillEntryDO() + dto.copyTo(entryDO) + return entryDO + } - /** - * LAYOUT List page - */ - override fun createListLayout(request: HttpServletRequest, layout: UILayout, magicFilter: MagicFilter, userAccess: UILayout.UserAccess) { - agGridSupport.prepareUIGrid4ListPage( - request, - layout, - magicFilter, - this, - userAccess = userAccess, - ) - .add(lc, "lastUpdate", "skill", "owner") - .add(lc, "rating", formatter = UIAgGridColumnDef.Formatter.RATING) - .add(lc, "interest", formatter = UIAgGridColumnDef.Formatter.RATING) - .add(lc, "comment") - .withMultiRowSelection(request, magicFilter) + /** + * LAYOUT List page + */ + override fun createListLayout( + request: HttpServletRequest, + layout: UILayout, + magicFilter: MagicFilter, + userAccess: UILayout.UserAccess + ) { + agGridSupport.prepareUIGrid4ListPage( + request, + layout, + magicFilter, + this, + userAccess = userAccess, + ) + .add(lc, "lastUpdate", "skill", "owner") + .add(lc, "rating", formatter = UIAgGridColumnDef.Formatter.RATING) + .add(lc, "interest", formatter = UIAgGridColumnDef.Formatter.RATING) + .add(lc, "comment") + .withMultiRowSelection(request, magicFilter) - layout.add( - MenuItem( - "skillmatrix.export", - i18nKey = "exportAsXls", - url = "${SkillMatrixServicesRest.REST_EXCEL_EXPORT_PATH}", - type = MenuItemTargetType.DOWNLOAD - ) - ) - } + layout.add( + MenuItem( + "skillmatrix.export", + i18nKey = "exportAsXls", + url = SkillMatrixServicesRest.REST_EXCEL_EXPORT_PATH, + type = MenuItemTargetType.DOWNLOAD + ) + ) + } - override fun addMagicFilterElements(elements: MutableList) { - elements.add( - UIFilterElement( - "owner", - UIFilterElement.FilterType.BOOLEAN, - translate("plugins.skillmatrix.filter.mySkills"), - defaultFilter = true - ) - ) - } + override fun addMagicFilterElements(elements: MutableList) { + elements.add( + UIFilterElement( + "owner", + UIFilterElement.FilterType.BOOLEAN, + translate("plugins.skillmatrix.filter.mySkills"), + defaultFilter = true + ) + ) + } - override fun preProcessMagicFilter( - target: QueryFilter, - source: MagicFilter - ): List>? { - val ownerFilterEntry = source.entries.find { it.field == "owner" } - if (ownerFilterEntry?.isTrueValue == true) { - ownerFilterEntry.synthetic = true - target.createJoin("owner") - target.add(QueryFilter.eq("owner", ThreadLocalUserContext.loggedInUser!!)) // Show only own skills, not from others. + override fun preProcessMagicFilter( + target: QueryFilter, + source: MagicFilter + ): List>? { + val ownerFilterEntry = source.entries.find { it.field == "owner" } + if (ownerFilterEntry?.isTrueValue == true) { + ownerFilterEntry.synthetic = true + target.createJoin("owner") + target.add( + QueryFilter.eq( + "owner", + ThreadLocalUserContext.loggedInUser!! + ) + ) // Show only own skills, not from others. + } + return null } - return null - } - /** - * Sets logged-in-user as owner. - */ - override fun prepareClone(dto: SkillEntryDO): SkillEntryDO { - val clone = super.prepareClone(dto) - clone.owner = ThreadLocalUserContext.loggedInUser - return clone - } + /** + * Sets logged-in-user as owner. + */ + override fun prepareClone(dto: SkillEntry): SkillEntry { + val clone = super.prepareClone(dto) + clone.owner = User(ThreadLocalUserContext.requiredLoggedInUser) + return clone + } - /** - * LAYOUT Edit page - */ - override fun createEditLayout(dto: SkillEntryDO, userAccess: UILayout.UserAccess): UILayout { - val skillRating = UIRatingStars( - "rating", - lc, - Array(SkillEntryDO.MAX_VAL_RATING + 1) { idx -> translate("plugins.skillmatrix.rating.$idx") }, - label = "plugins.skillmatrix.rating" - ) - val interestRating = UIRatingStars( - "interest", - lc, - Array(SkillEntryDO.MAX_VAL_INTEREST + 1) { idx -> translate("plugins.skillmatrix.interest.$idx") }, - label = "plugins.skillmatrix.interest" - ) - val skill = UIInput("skill", lc).enableAutoCompletion(this) - val layout = super.createEditLayout(dto, userAccess) - .add(skill) - .add( - UIRow() - .add( - UICol(UILength(md = 4)).add( - UIReadOnlyField( - "owner.displayName", - lc, - label = "plugins.skillmatrix.owner" - ) + /** + * LAYOUT Edit page + */ + override fun createEditLayout(dto: SkillEntry, userAccess: UILayout.UserAccess): UILayout { + val skillRating = UIRatingStars( + "rating", + lc, + Array(SkillEntryDO.MAX_VAL_RATING + 1) { idx -> translate("plugins.skillmatrix.rating.$idx") }, + label = "plugins.skillmatrix.rating" + ) + val interestRating = UIRatingStars( + "interest", + lc, + Array(SkillEntryDO.MAX_VAL_INTEREST + 1) { idx -> translate("plugins.skillmatrix.interest.$idx") }, + label = "plugins.skillmatrix.interest" + ) + val skill = UIInput("skill", lc).enableAutoCompletion(this) + val layout = super.createEditLayout(dto, userAccess) + .add(skill) + .add( + UIRow() + .add( + UICol(UILength(md = 4)).add( + UIReadOnlyField( + "owner.displayName", + lc, + label = "plugins.skillmatrix.owner" + ) + ) + ) + .add(UICol(UILength(md = 4)).add(skillRating)) + .add(UICol(UILength(md = 4)).add(interestRating)) ) - ) - .add(UICol(UILength(md = 4)).add(skillRating)) - .add(UICol(UILength(md = 4)).add(interestRating)) - ) - .add(lc, "comment") - return LayoutUtils.processEditPage(layout, dto, this) - } + .add(lc, "comment") + return LayoutUtils.processEditPage(layout, dto, this) + } } From 15a4494431aa947ca995f7b9aa2ef4689ffea500 Mon Sep 17 00:00:00 2001 From: Kai Reinhard Date: Thu, 23 Jan 2025 22:53:13 +0100 Subject: [PATCH 3/4] UserPrefEditForm fixed: creation of task favorites. --- .../web/user/UserPrefEditForm.java | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/projectforge-wicket/src/main/java/org/projectforge/web/user/UserPrefEditForm.java b/projectforge-wicket/src/main/java/org/projectforge/web/user/UserPrefEditForm.java index 475b97c983..22e782f389 100644 --- a/projectforge-wicket/src/main/java/org/projectforge/web/user/UserPrefEditForm.java +++ b/projectforge-wicket/src/main/java/org/projectforge/web/user/UserPrefEditForm.java @@ -235,8 +235,7 @@ void createParameterRepeaterChildren() WicketSupport.get(UserPrefDao.class).updateParameterValueObject(param); if (PFUserDO.class.isAssignableFrom(param.getType()) == true) { final UserSelectPanel userSelectPanel = new UserSelectPanel(fs.newChildId(), - new UserPrefPropertyModel(userPrefDao, - param, "valueAsObject"), + new UserPrefPropertyModel(param, "valueAsObject"), parentPage, param.getParameter()); if (data.getAreaObject() == UserPrefArea.USER_FAVORITE) { userSelectPanel.setShowFavorites(false); @@ -245,8 +244,7 @@ void createParameterRepeaterChildren() userSelectPanel.init(); } else if (TaskDO.class.isAssignableFrom(param.getType()) == true) { final TaskSelectPanel taskSelectPanel = new TaskSelectPanel(fs, - new UserPrefPropertyModel(userPrefDao, param, - "valueAsObject"), + new UserPrefPropertyModel(param, "valueAsObject"), parentPage, param.getParameter()); if (data.getAreaObject() == UserPrefArea.TASK_FAVORITE) { taskSelectPanel.setShowFavorites(false); @@ -255,8 +253,7 @@ void createParameterRepeaterChildren() taskSelectPanel.init(); } else if (GroupDO.class.isAssignableFrom(param.getType()) == true) { final NewGroupSelectPanel groupSelectPanel = new NewGroupSelectPanel(fs.newChildId(), - new UserPrefPropertyModel( - userPrefDao, param, "valueAsObject"), + new UserPrefPropertyModel(param, "valueAsObject"), parentPage, param.getParameter()); fs.add(groupSelectPanel); groupSelectPanel.init(); @@ -286,8 +283,7 @@ protected void setKost2Id(final Long kost2Id) dependentsMap.put(param.getParameter(), kost2DropDownChoice); } else if (ProjektDO.class.isAssignableFrom(param.getType()) == true) { final NewProjektSelectPanel projektSelectPanel = new NewProjektSelectPanel(fs.newChildId(), - new UserPrefPropertyModel( - userPrefDao, param, "valueAsObject"), + new UserPrefPropertyModel(param, "valueAsObject"), parentPage, param.getParameter()); if (data.getAreaObject() == UserPrefArea.PROJEKT_FAVORITE) { projektSelectPanel.setShowFavorites(false); @@ -296,8 +292,7 @@ protected void setKost2Id(final Long kost2Id) projektSelectPanel.init(); } else if (KundeDO.class.isAssignableFrom(param.getType()) == true) { final NewCustomerSelectPanel kundeSelectPanel = new NewCustomerSelectPanel(fs.newChildId(), - new UserPrefPropertyModel( - userPrefDao, param, "valueAsObject"), + new UserPrefPropertyModel(param, "valueAsObject"), null, parentPage, param.getParameter()); if (data.getAreaObject() == UserPrefArea.KUNDE_FAVORITE) { kundeSelectPanel.setShowFavorites(false); @@ -315,7 +310,7 @@ protected void setKost2Id(final Long kost2Id) final LabelValueChoiceRenderer choiceRenderer = new LabelValueChoiceRenderer(this, (I18nEnum[]) param.getType().getEnumConstants()); final DropDownChoice choice = new DropDownChoice(fs.getDropDownChoiceId(), - new UserPrefPropertyModel(userPrefDao, param, "valueAsObject"), choiceRenderer.getValues(), + new UserPrefPropertyModel(param, "valueAsObject"), choiceRenderer.getValues(), choiceRenderer); choice.setNullValid(true); fs.add(choice); @@ -349,15 +344,12 @@ private class UserPrefPropertyModel extends PropertyModel { private static final long serialVersionUID = 6644505091461853375L; - private final UserPrefDao userPrefDao; - private final UserPrefEntryDO userPrefEntry; - public UserPrefPropertyModel(final UserPrefDao userPrefDao, final UserPrefEntryDO userPrefEntry, + public UserPrefPropertyModel(final UserPrefEntryDO userPrefEntry, final String expression) { super(userPrefEntry, expression); - this.userPrefDao = userPrefDao; this.userPrefEntry = userPrefEntry; } @@ -365,7 +357,7 @@ public UserPrefPropertyModel(final UserPrefDao userPrefDao, final UserPrefEntryD public void setObject(final T object) { super.setObject(object); - userPrefDao.setValueObject(userPrefEntry, object); + WicketSupport.get(UserPrefDao.class).setValueObject(userPrefEntry, object); } } } From e1960701c190a7be31f1eb50b50c00fac698ac68 Mon Sep 17 00:00:00 2001 From: Kai Reinhard Date: Thu, 23 Jan 2025 23:48:23 +0100 Subject: [PATCH 4/4] Bugfix: MonthlyEmployeeReportWeek: sum fixed. --- .../projectforge/business/fibu/MonthlyEmployeeReportWeek.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projectforge-business/src/main/java/org/projectforge/business/fibu/MonthlyEmployeeReportWeek.java b/projectforge-business/src/main/java/org/projectforge/business/fibu/MonthlyEmployeeReportWeek.java index 081070f3de..365719c301 100644 --- a/projectforge-business/src/main/java/org/projectforge/business/fibu/MonthlyEmployeeReportWeek.java +++ b/projectforge-business/src/main/java/org/projectforge/business/fibu/MonthlyEmployeeReportWeek.java @@ -112,7 +112,7 @@ void addEntry(TimesheetDO sheet, final boolean hasSelectAccess) { } long duration = sheet.getDuration(); entry.addMillis(duration); - totalDuration += entry.getWorkFractionMillis(); + totalDuration += sheet.getWorkFractionDuration(); totalGrossDuration += duration; }