fix(distro-ddl): add user preferences as `@OneToMany` relationship to UserBean
MODIFY
distro/data/src/main/resources/ddls/apiman_h2.ddl
distro/data/src/main/resources/ddls/apiman_h2.ddl
-- Update Database Script
-- *********************************************************************
-- Change Log: /Users/msavy/oss/apiman/apiman/distro/ddl/src/main/liquibase/master.xml
--- Ran at: 02/03/2022, 10:34
+-- Ran at: 02/03/2022, 15:15
-- Against: sa@offline:h2?version=1.4.199&caseSensitive=true&changeLogFile=/Users/msavy/oss/apiman/apiman/distro/ddl/target/changelog/h2/databasechangelog.csv
-- Liquibase version: 4.6.2
-- *********************************************************************
@@ -363,3 +363,6 @@ CREATE TABLE notification_rules (NotificationPreferenceEntity_id BIGINT NOT NULL
ALTER TABLE notification_rules ADD CONSTRAINT FKbxdud6qk8e28eq1mjihqauybo FOREIGN KEY (NotificationPreferenceEntity_id) REFERENCES notification_preferences (id);
+-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646232783603-7::msavy (generated)
+ALTER TABLE notification_preferences ADD CONSTRAINT FKt9qjvmcl36i14utm5uptyqg84 FOREIGN KEY (user_id) REFERENCES users (username);
+

MODIFY
distro/data/src/main/resources/ddls/apiman_mssql15.ddl
distro/data/src/main/resources/ddls/apiman_mssql15.ddl
-- Update Database Script
-- *********************************************************************
-- Change Log: /Users/msavy/oss/apiman/apiman/distro/ddl/src/main/liquibase/master.xml
--- Ran at: 02/03/2022, 10:34
+-- Ran at: 02/03/2022, 15:15
-- Against: apiman@offline:mssql?version=15&caseSensitive=true&catalog=apiman&changeLogFile=/Users/msavy/oss/apiman/apiman/distro/ddl/target/changelog/mssql/databasechangelog.csv
-- Liquibase version: 4.6.2
-- *********************************************************************
@@ -498,3 +498,7 @@ DROP TABLE notification_category_preferences
DROP TABLE notification_types
GO
+-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646232783603-7::msavy (generated)
+ALTER TABLE notification_preferences ADD CONSTRAINT FKt9qjvmcl36i14utm5uptyqg84 FOREIGN KEY (user_id) REFERENCES users (username)
+GO
+

MODIFY
distro/data/src/main/resources/ddls/apiman_mysql8.ddl
distro/data/src/main/resources/ddls/apiman_mysql8.ddl
-- Update Database Script
-- *********************************************************************
-- Change Log: /Users/msavy/oss/apiman/apiman/distro/ddl/src/main/liquibase/master.xml
--- Ran at: 02/03/2022, 10:34
+-- Ran at: 02/03/2022, 15:15
-- Against: apiman@offline:mysql?version=8&caseSensitive=true&catalog=apiman&changeLogFile=/Users/msavy/oss/apiman/apiman/distro/ddl/target/changelog/mysql/databasechangelog.csv
-- Liquibase version: 4.6.2
-- *********************************************************************
@@ -378,3 +378,6 @@ DROP TABLE notification_category_preferences;
-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646057700977-11::msavy (generated)
DROP TABLE notification_types;
+-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646232783603-7::msavy (generated)
+ALTER TABLE notification_preferences ADD CONSTRAINT FKt9qjvmcl36i14utm5uptyqg84 FOREIGN KEY (user_id) REFERENCES users (username);
+

MODIFY
distro/data/src/main/resources/ddls/apiman_oracle19.ddl
distro/data/src/main/resources/ddls/apiman_oracle19.ddl
-- Update Database Script
-- *********************************************************************
-- Change Log: /Users/msavy/oss/apiman/apiman/distro/ddl/src/main/liquibase/master.xml
--- Ran at: 02/03/2022, 10:34
+-- Ran at: 02/03/2022, 15:15
-- Against: apiman@offline:oracle?version=19&caseSensitive=true&changeLogFile=/Users/msavy/oss/apiman/apiman/distro/ddl/target/changelog/oracle/databasechangelog.csv
-- Liquibase version: 4.6.2
-- *********************************************************************
@@ -373,3 +373,6 @@ DROP TABLE notification_category_preferences;
-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646057700977-11::msavy (generated)
DROP TABLE notification_types;
+-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646232783603-7::msavy (generated)
+ALTER TABLE notification_preferences ADD CONSTRAINT FKt9qjvmcl36i14utm5uptyqg84 FOREIGN KEY (user_id) REFERENCES users (username);
+

MODIFY
distro/data/src/main/resources/ddls/apiman_postgresql9.ddl
distro/data/src/main/resources/ddls/apiman_postgresql9.ddl
-- Update Database Script
-- *********************************************************************
-- Change Log: /Users/msavy/oss/apiman/apiman/distro/ddl/src/main/liquibase/master.xml
--- Ran at: 02/03/2022, 12:24
+-- Ran at: 02/03/2022, 15:15
-- Against: apiman@offline:postgresql?version=9.6.23&caseSensitive=true&changeLogFile=/Users/msavy/oss/apiman/apiman/distro/ddl/target/changelog/postgresql/databasechangelog.csv
-- Liquibase version: 4.6.2
-- *********************************************************************
@@ -416,3 +416,6 @@ DROP TABLE notification_category_preferences;
-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646057700977-11::msavy (generated)
DROP TABLE notification_types;
+-- Changeset src/main/liquibase/current/20220228-rework-notification-filtering.xml::1646232783603-7::msavy (generated)
+ALTER TABLE notification_preferences ADD CONSTRAINT "FKt9qjvmcl36i14utm5uptyqg84" FOREIGN KEY (user_id) REFERENCES users (username);
+

MODIFY
distro/ddl/src/main/liquibase/current/20220228-rework-notification-filtering.xml
distro/ddl/src/main/liquibase/current/20220228-rework-notification-filtering.xml
<changeSet author="msavy (generated)" id="1646057700977-11">
<dropTable tableName="notification_types"/>
</changeSet>
+ <changeSet author="msavy (generated)" id="1646232783603-7">
+ <addForeignKeyConstraint baseColumnNames="user_id" baseTableName="notification_preferences" constraintName="FKt9qjvmcl36i14utm5uptyqg84" deferrable="false" initiallyDeferred="false" referencedColumnNames="username" referencedTableName="users" validate="true"/>
+ </changeSet>
</databaseChangeLog>

MODIFY
manager/api/beans/src/main/java/io/apiman/manager/api/beans/idm/UserBean.java
manager/api/beans/src/main/java/io/apiman/manager/api/beans/idm/UserBean.java
package io.apiman.manager.api.beans.idm;
+import io.apiman.manager.api.beans.notifications.NotificationPreferenceEntity;
+
import java.io.Serializable;
import java.util.Date;
+import java.util.HashSet;
import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
import java.util.StringJoiner;
+import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
+import javax.persistence.FetchType;
import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
@@ -46,8 +55,10 @@ public class UserBean implements Serializable {
private String email;
@Column(name = "joined_on", updatable=false)
private Date joinedOn;
- @Column(name = "locale") // TODO: maybe should be required and we can fix through import migration?
+ @Column(name = "locale")
private String locale = Locale.getDefault().toLanguageTag();
+ @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ private Set<NotificationPreferenceEntity> notificationPreferences = new HashSet<>();
// Used only when returning information about the current user
@Transient
@@ -115,35 +126,13 @@ public class UserBean implements Serializable {
this.joinedOn = joinedOn;
}
- /**
- * @see java.lang.Object#hashCode()
- */
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((username == null) ? 0 : username.hashCode());
- return result;
+ public Set<NotificationPreferenceEntity> getNotificationPreferences() {
+ return notificationPreferences;
}
- /**
- * @see java.lang.Object#equals(java.lang.Object)
- */
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- UserBean other = (UserBean) obj;
- if (username == null) {
- if (other.username != null)
- return false;
- } else if (!username.equals(other.username))
- return false;
- return true;
+ public UserBean setNotificationPreferences(Set<NotificationPreferenceEntity> notificationPreferences) {
+ this.notificationPreferences = notificationPreferences;
+ return this;
}
/**
@@ -180,6 +169,23 @@ public class UserBean implements Serializable {
this.locale = locale;
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ UserBean userBean = (UserBean) o;
+ return Objects.equals(username, userBean.username);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(username);
+ }
+
@Override
public String toString() {
return new StringJoiner(", ", UserBean.class.getSimpleName() + "[", "]")

MODIFY
manager/api/beans/src/main/java/io/apiman/manager/api/beans/notifications/NotificationPreferenceEntity.java
manager/api/beans/src/main/java/io/apiman/manager/api/beans/notifications/NotificationPreferenceEntity.java
package io.apiman.manager.api.beans.notifications;
+import io.apiman.manager.api.beans.idm.UserBean;
+
import java.util.LinkedHashSet;
-import java.util.Objects;
import java.util.Set;
-import java.util.StringJoiner;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
@@ -13,12 +13,12 @@ import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
-import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
-import org.hibernate.Hibernate;
import org.hibernate.annotations.NaturalId;
/**
@@ -46,10 +46,10 @@ public class NotificationPreferenceEntity {
@GeneratedValue
private Long id;
- @Column(name = "user_id", nullable = false)
- @NotBlank
+ @ManyToOne
+ @JoinColumn(name = "user_id")
@NaturalId
- private String userId; // TODO(msavy): explicitly link to user object, so we can cascade delete?
+ private UserBean user;
@Column(name = "type", nullable = false)
@NotNull
@@ -72,12 +72,12 @@ public class NotificationPreferenceEntity {
return this;
}
- public String getUserId() {
- return userId;
+ public UserBean getUser() {
+ return user;
}
- public NotificationPreferenceEntity setUserId(String userId) {
- this.userId = userId;
+ public NotificationPreferenceEntity setUser(UserBean user) {
+ this.user = user;
return this;
}
@@ -99,32 +99,4 @@ public class NotificationPreferenceEntity {
return this;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
- return false;
- }
- NotificationPreferenceEntity that = (NotificationPreferenceEntity) o;
- return userId != null && Objects.equals(userId, that.userId)
- && type != null && Objects.equals(type, that.type);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(userId, type);
- }
-
- @Override
- public String toString() {
- return new StringJoiner(", ", NotificationPreferenceEntity.class.getSimpleName() + "[", "]")
- .add("id=" + id)
- .add("userId='" + userId + "'")
- .add("type='" + type + "'")
- .toString();
- }
-
-
}

MODIFY
manager/api/core/src/main/java/io/apiman/manager/api/core/IStorage.java
manager/api/core/src/main/java/io/apiman/manager/api/core/IStorage.java
@@ -217,4 +217,11 @@ public interface IStorage {
List<UserBean> getAllUsersWithRole(String roleName, String orgName) throws StorageException;
void flush();
+
+ /**
+ * Get an object reference by its key without speaking to the database (key-only skeleton).
+ *
+ * <p>This is particularly useful when you want to persist a related entity without first having to look it up in the database.
+ */
+ <T> T getEntityObjectReference(Class<T> klazz, Object primaryKey);
}

MODIFY
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/JpaStorage.java
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/JpaStorage.java
@@ -2428,6 +2428,11 @@ public class JpaStorage extends AbstractJpaStorage implements IStorage, IStorage
getActiveEntityManager().flush();
}
+ @Override
+ public <T> T getEntityObjectReference(Class<T> klazz, Object primaryKey) {
+ return getActiveEntityManager().getReference(klazz, primaryKey);
+ }
+
private void deleteAllPolicies(OrganizationBean organizationBean) throws StorageException {
deleteAllPolicies(organizationBean, null);
}

MODIFY
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/NotificationRepositoryImpl.java
manager/api/jpa/src/main/java/io/apiman/manager/api/jpa/NotificationRepositoryImpl.java
@@ -163,7 +163,7 @@ public class NotificationRepositoryImpl extends AbstractJpaStorage implements IN
@Override
public Optional<NotificationPreferenceEntity> getNotificationPreferenceByUserId(String userId) {
Query query = getActiveEntityManager()
- .createQuery("SELECT pref FROM NotificationPreferenceEntity pref WHERE pref.userId = :userId")
+ .createQuery("SELECT pref FROM NotificationPreferenceEntity pref WHERE pref.user.username = :userId")
.setParameter("userId", userId);
return super.getOne(query);
}
@@ -174,7 +174,7 @@ public class NotificationRepositoryImpl extends AbstractJpaStorage implements IN
.createQuery(
"SELECT pref "
+ "FROM NotificationPreferenceEntity pref "
- + "WHERE pref.userId = :userId "
+ + "WHERE pref.user.username = :userId "
+ "AND pref.type = :notificationType"
)
.setParameter("userId", userId)

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/NotificationRulesService.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/NotificationRulesService.java
@@ -43,7 +43,7 @@ public class NotificationRulesService {
}
public NotificationRulesService cache(NotificationPreferenceEntity npe) {
- RuleKey key = RuleKey.of(npe.getUserId(), npe.getType());
+ RuleKey key = RuleKey.of(npe.getUser().getUsername(), npe.getType());
notificationRepository.getNotificationPreferenceByUserIdAndType(key.userId, key.nType).map(entity -> buildRuleGroup(entity, key));
return this;
}

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/service/NotificationService.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/service/NotificationService.java
@@ -7,6 +7,7 @@ import io.apiman.common.util.JsonUtil;
import io.apiman.common.util.Preconditions;
import io.apiman.manager.api.beans.events.IVersionedApimanEvent;
import io.apiman.manager.api.beans.idm.PermissionType;
+import io.apiman.manager.api.beans.idm.UserBean;
import io.apiman.manager.api.beans.idm.UserDto;
import io.apiman.manager.api.beans.idm.UserMapper;
import io.apiman.manager.api.beans.notifications.NotificationEntity;
@@ -237,7 +238,7 @@ public class NotificationService implements DataAccessUtilMixin {
.orElseGet(() -> {
NotificationPreferenceEntity newNpe = new NotificationPreferenceEntity()
.setType(createFilter.getNotificationType())
- .setUserId(userId);
+ .setUser(storage.getEntityObjectReference(UserBean.class, userId));
tryAction(() -> notificationRepository.create(newNpe));
return newNpe;
});

fix(distro-ddl): add user preferences as `@OneToMany` relationship to UserBean