Skip to content

Commit

Permalink
Encapsulate JpaTarget update listening in vendor specific modules
Browse files Browse the repository at this point in the history
+ add EclipseLink tenant cleanup after transaction

Signed-off-by: Avgustin Marinov <[email protected]>
  • Loading branch information
avgustinmm committed Dec 13, 2024
1 parent eeeae31 commit 7a9e7d8
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 50 deletions.
6 changes: 0 additions & 6 deletions hawkbit-repository/hawkbit-repository-jpa-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,5 @@
<artifactId>hibernate-jpamodelgen</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>${eclipselink.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.Serializable;
import java.util.concurrent.TimeUnit;

import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
Expand All @@ -21,12 +22,14 @@
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.context.SecurityContextHolder;

/**
* Core information of all entities.
*/
@MappedSuperclass
@EntityListeners({ AuditingEntityListener.class, EntityInterceptorListener.class })
public abstract class AbstractBaseEntity implements BaseEntity, Serializable {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
import java.util.Objects;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.transaction.Transaction;

import org.eclipse.hawkbit.repository.jpa.model.EntityPropertyChangeListener;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEventManager;
import org.eclipse.persistence.sessions.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.orm.jpa.JpaTransactionManager;
Expand All @@ -27,6 +30,8 @@
/**
* {@link JpaTransactionManager} that sets the {@link TenantAware#getCurrentTenant()} in the eclipselink session. This has
* to be done in eclipselink after a {@link Transaction} has been started.
* <p/>
* The class also handles setting the {@link EntityPropertyChangeListener} to the {@link DescriptorEventManager} of the
*/
class MultiTenantJpaTransactionManager extends JpaTransactionManager {

Expand All @@ -36,18 +41,64 @@ class MultiTenantJpaTransactionManager extends JpaTransactionManager {
@Autowired
private transient TenantAware tenantAware;

private static final Class<?> JPA_TARGET;

static {
try {
JPA_TARGET = Class.forName("org.eclipse.hawkbit.repository.jpa.model.JpaTarget");
} catch (final RuntimeException e) {
throw e;
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}

private static final EntityPropertyChangeListener ENTITY_PROPERTY_CHANGE_LISTENER = new EntityPropertyChangeListener();

@Override
protected void doBegin(final Object transaction, final TransactionDefinition definition) {
super.doBegin(transaction, definition);

final EntityManager em = Objects.requireNonNull(
(EntityManagerHolder) TransactionSynchronizationManager.getResource(
Objects.requireNonNull(
getEntityManagerFactory(),
"No EntityManagerFactory provided by TransactionSynchronizationManager")),
"No EntityManagerHolder provided by TransactionSynchronizationManager")
.getEntityManager();

final ClassDescriptor classDescriptor = em.unwrap(Session.class).getClassDescriptor(JPA_TARGET);
if (classDescriptor != null) {
final DescriptorEventManager dem = classDescriptor.getEventManager();
if (dem != null && !dem.getEventListeners().contains(ENTITY_PROPERTY_CHANGE_LISTENER)) {
dem.addListener(ENTITY_PROPERTY_CHANGE_LISTENER);
}
}

final String currentTenant = tenantAware.getCurrentTenant();
if (currentTenant != null) {
final EntityManagerFactory emFactory = Objects.requireNonNull(getEntityManagerFactory());
final EntityManagerHolder emHolder = Objects.requireNonNull(
(EntityManagerHolder) TransactionSynchronizationManager.getResource(emFactory),
"No EntityManagerHolder provided by TransactionSynchronizationManager");
final EntityManager em = emHolder.getEntityManager();
if (currentTenant == null) {
cleanupTenant(em);
} else {
em.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, currentTenant.toUpperCase());
}
}

@Override
protected void doCleanupAfterCompletion(final Object transaction) {
final EntityManager em = Objects.requireNonNull(
(EntityManagerHolder) TransactionSynchronizationManager.getResource(
Objects.requireNonNull(
getEntityManagerFactory(),
"No EntityManagerFactory provided by TransactionSynchronizationManager")),
"No EntityManagerHolder provided by TransactionSynchronizationManager")
.getEntityManager();
super.doCleanupAfterCompletion(transaction);
cleanupTenant(em);
}

private void cleanupTenant(final EntityManager em) {
if (em.isOpen() && em.getProperties().containsKey(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT)) {
em.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,26 @@
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.Version;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.context.SecurityContextHolder;

/**
* Base hawkBit entity class containing the common attributes for EclipseLink.
*/
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Default constructor needed for JPA entities.
@MappedSuperclass
@EntityListeners({ AuditingEntityListener.class, EntityInterceptorListener.class })
public abstract class AbstractJpaBaseEntity extends AbstractBaseEntity {

protected static final int USERNAME_FIELD_LENGTH = 64;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.queries.UpdateObjectQuery;

import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;

/**
* Listens to updates on <code></code>JpaTarget</code> entities, filtering out updates that only change the
* "lastTargetQuery" or "address" fields.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
*/
package org.eclipse.hawkbit.repository.jpa;

import java.util.Collection;
import java.util.List;
import java.util.stream.IntStream;

import jakarta.persistence.Query;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import org.eclipse.hawkbit.repository.jpa.model.EntityPropertyChangeListener;

import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.cfg.AvailableSettings;
Expand Down Expand Up @@ -95,8 +94,8 @@ public void integrate(
}

@Override
public void disintegrate(
final SessionFactoryImplementor sessionFactory, final SessionFactoryServiceRegistry serviceRegistry) {
public void disintegrate(final SessionFactoryImplementor sessionFactory, final SessionFactoryServiceRegistry serviceRegistry) {
// do nothing
}
}));
return properties;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,26 @@
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;
import jakarta.persistence.Version;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.repository.jpa.model.helper.AfterTransactionCommitExecutorHolder;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.context.SecurityContextHolder;

/**
* Base hawkBit entity class containing the common attributes for Hibernate.
*/
@NoArgsConstructor(access = AccessLevel.PROTECTED) // Default constructor needed for JPA entities.
@MappedSuperclass
@EntityListeners({ AuditingEntityListener.class, EntityInterceptorListener.class })
public abstract class AbstractJpaBaseEntity extends AbstractBaseEntity {

protected static final int USERNAME_FIELD_LENGTH = 64;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@

import java.util.List;

import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventAdapter;
import org.eclipse.persistence.queries.UpdateObjectQuery;

import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
import jakarta.persistence.Converter;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Index;
Expand Down Expand Up @@ -82,7 +81,6 @@
uniqueConstraints = @UniqueConstraint(columnNames = { "controller_id", "tenant" }, name = "uk_tenant_controller_id"))
// exception squid:S2160 - BaseEntity equals/hashcode is handling correctly for sub entities
@SuppressWarnings("squid:S2160")
@EntityListeners({ EntityPropertyChangeListener.class }) // add listener to the listeners declared into suppers
@Slf4j
public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAwareEntity {

Expand Down

0 comments on commit 7a9e7d8

Please sign in to comment.