Skip to content

Commit

Permalink
chore: Handle cacheable repo methods
Browse files Browse the repository at this point in the history
  • Loading branch information
abhvsn committed Nov 27, 2024
1 parent 1d49fab commit 24495f7
Show file tree
Hide file tree
Showing 18 changed files with 87 additions and 154 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Optional<T> updateById(

T setUserPermissionsInObject(T obj, Collection<String> permissionGroups);

T setUserPermissionsInObject(T obj, User user);
T setUserPermissionsInObject(T obj, User user, EntityManager entityManager);

T updateAndReturn(
String id, BridgeUpdate updateObj, AclPermission permission, User currentUser, EntityManager entityManager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
import com.appsmith.server.repositories.BaseRepository;
import com.appsmith.server.repositories.CacheableRepositoryHelper;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Transient;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
Expand All @@ -24,13 +22,10 @@
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.lang.reflect.Field;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

Expand Down Expand Up @@ -202,57 +197,18 @@ public Mono<T> save(T entity) {
});
}

/**
* Copy the values of fields marked with {@link Transient} annotation from {@code source} to {@code target}.
* This is also done recursively, for nested object fields as well, unless {@code nest} is 0.
*/
private static <T> void copyTransientFieldValues(T source, T target, int nest) throws IllegalAccessException {
final Class<?> cls = source.getClass();

// Get all non-transient field names
final List<Field> nonTransientFields = getAllFields(cls).stream()
.filter(field -> field.getAnnotation(Transient.class) == null)
.toList();

final List<String> nonTransientFieldNames =
nonTransientFields.stream().map(Field::getName).toList();

// merge latest database updated source object with the transient fields
BeanUtils.copyProperties(source, target, nonTransientFieldNames.toArray(new String[0]));

if (nest > 0) {
--nest;
for (final Field nonTransientField : nonTransientFields) {
nonTransientField.setAccessible(true);
final Object sourceValue = nonTransientField.get(source);
if (sourceValue == null) {
continue;
}
copyTransientFieldValues(sourceValue, nonTransientField.get(target), nest);
}
}
}

public static List<Field> getAllFields(Class<?> type) {
List<Field> fields = new ArrayList<>();
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
fields.addAll(Arrays.asList(c.getDeclaredFields()));
}
return fields;
}

private String generateId() {
return UUID.randomUUID().toString();
}

public Mono<T> findById(String id, AclPermission permission) {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> (User) ctx.getAuthentication().getPrincipal())
.flatMap(this::getAllPermissionGroupsForUser)
.map(ArrayList::new)
.zipWith(Mono.deferContextual(ctx -> Mono.just(ctx.getOrDefault(TX_CONTEXT, entityManager))))
.flatMap(tuple -> this.getAllPermissionGroupsForUser(tuple.getT1(), tuple.getT2())
.zipWith(Mono.just(tuple.getT2())))
.map(tuple2 -> {
final ArrayList<String> permissionGroups = tuple2.getT1();
final ArrayList<String> permissionGroups = new ArrayList<>(tuple2.getT1());
final EntityManager em = tuple2.getT2();
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<T> cq = cb.createQuery(genericDomain);
Expand All @@ -278,13 +234,13 @@ public Mono<T> findById(String id, AclPermission permission) {
}

// FIXME: Duplicate from BaseAppsmithRepositoryCEImpl
private Mono<Set<String>> getAllPermissionGroupsForUser(User user) {
private Mono<Set<String>> getAllPermissionGroupsForUser(User user, EntityManager em) {
if (user.getTenantId() == null) {
user.setTenantId(cacheableRepositoryHelper.getDefaultTenantId().block());
}

return Mono.zip(
cacheableRepositoryHelper.getPermissionGroupsOfUser(user),
cacheableRepositoryHelper.getPermissionGroupsOfUser(user, em),
cacheableRepositoryHelper.getPermissionGroupsOfAnonymousUser())
.map(tuple -> {
final Set<String> permissionGroups = new HashSet<>(tuple.getT1());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,30 +254,32 @@ public Optional<Integer> updateFieldById(
return Optional.of(count);
}

protected Set<String> getCurrentUserPermissionGroupsIfRequired(AclPermission permission, User user) {
return getCurrentUserPermissionGroupsIfRequired(Optional.ofNullable(permission), user, true);
protected Set<String> getCurrentUserPermissionGroupsIfRequired(
AclPermission permission, User user, EntityManager em) {
return getCurrentUserPermissionGroupsIfRequired(Optional.ofNullable(permission), user, true, em);
}

protected Set<String> getCurrentUserPermissionGroupsIfRequired(
Optional<AclPermission> permission, User user, boolean includeAnonymousUserPermissions) {
Optional<AclPermission> permission, User user, boolean includeAnonymousUserPermissions, EntityManager em) {
// Expect a valid AclPermission and a user to fetch valid permission groups
if (permission.isEmpty()) {
return Set.of();
}
return getPermissionGroupsForUser(user, includeAnonymousUserPermissions);
return getPermissionGroupsForUser(user, includeAnonymousUserPermissions, em);
}

public Set<String> getPermissionGroupsForUser(User user) {
return getPermissionGroupsForUser(user, true);
public Set<String> getPermissionGroupsForUser(User user, EntityManager em) {
return getPermissionGroupsForUser(user, true, em);
}

protected Set<String> getPermissionGroupsForUser(User user, boolean includeAnonymousUserPermissions) {
protected Set<String> getPermissionGroupsForUser(
User user, boolean includeAnonymousUserPermissions, EntityManager em) {
if (!isValidUser(user)) {
return Set.of();
}
final Set<String> permissionGroups = includeAnonymousUserPermissions
? getAllPermissionGroupsForUser(user)
: getStrictPermissionGroupsForUser(user);
? getAllPermissionGroupsForUser(user, em)
: getStrictPermissionGroupsForUser(user, em);
return permissionGroups == null ? Collections.emptySet() : permissionGroups;
}

Expand All @@ -295,16 +297,15 @@ public List<T> queryAllExecute(QueryAllParams<T> params) {
@SneakyThrows
@SuppressWarnings("unchecked")
public <P> List<P> queryAllExecute(QueryAllParams<T> params, Class<P> projectionClass) {
EntityManager em = getEntityManager(params);
return Mono.justOrEmpty(params.getPermissionGroups())
.switchIfEmpty(Mono.defer(() ->
Mono.just(getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser()))))
.switchIfEmpty(Mono.defer(() -> Mono.just(
getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser(), em))))
.map(ArrayList::new)
.flatMap(permissionGroups -> {
if (params.getPermission() != null && permissionGroups.isEmpty()) {
return Mono.just(Collections.<P>emptyList());
}

EntityManager em = params.getEntityManager() == null ? entityManager : params.getEntityManager();
final CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<?> cq = cb.createQuery(projectionClass);

Expand Down Expand Up @@ -383,15 +384,15 @@ public Optional<T> queryOneExecute(QueryAllParams<T> params) {

@SuppressWarnings("unchecked")
public <P> Optional<P> queryOneExecute(QueryAllParams<T> params, Class<P> projectionClass) {
EntityManager em = getEntityManager(params);
return Mono.justOrEmpty(params.getPermissionGroups())
.switchIfEmpty(Mono.defer(() ->
Mono.just(getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser()))))
.switchIfEmpty(Mono.defer(() -> Mono.just(
getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser(), em))))
.map(ArrayList::new)
.flatMap(permissionGroups -> {
if (params.getPermission() != null && permissionGroups.isEmpty()) {
return Mono.empty();
}
EntityManager em = params.getEntityManager() == null ? entityManager : params.getEntityManager();
final CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<?> cq = cb.createQuery(projectionClass);
// We are creating the query with generic return type as Object[] and then mapping it to the
Expand Down Expand Up @@ -445,15 +446,15 @@ public Optional<T> queryFirstExecute(QueryAllParams<T> params) {
}

public Optional<Long> countExecute(QueryAllParams<T> params) {
EntityManager em = getEntityManager(params);
return Mono.justOrEmpty(params.getPermissionGroups())
.switchIfEmpty(Mono.defer(() ->
Mono.just(getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser()))))
.switchIfEmpty(Mono.defer(() -> Mono.just(
getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser(), em))))
.map(ArrayList::new)
.flatMap(permissionGroups -> {
if (params.getPermission() != null && permissionGroups.isEmpty()) {
return Mono.just(0L);
}
EntityManager em = params.getEntityManager() == null ? entityManager : params.getEntityManager();
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Long> cq = cb.createQuery(Long.class);
final Root<T> root = cq.from(genericDomain);
Expand Down Expand Up @@ -482,6 +483,8 @@ public Optional<Long> countExecute(QueryAllParams<T> params) {
.blockOptional();
}

@Transactional
@Modifying
public int updateExecute(@NonNull QueryAllParams<T> params, @NonNull T resource) {
// In case the update is not used to update the policies, then set the policies to null to ensure that the
// existing policies are not overwritten.
Expand Down Expand Up @@ -535,12 +538,12 @@ private Mono<Void> ensurePermissionGroupsInParams(QueryAllParams<T> params) {
if (!CollectionUtils.isEmpty(params.getPermissionGroups())) {
return Mono.empty();
}

return Mono.justOrEmpty(params.getPermissionGroups())
.switchIfEmpty(Mono.fromSupplier(() -> getCurrentUserPermissionGroupsIfRequired(
Optional.ofNullable(params.getPermission()),
params.getUser(),
params.isIncludeAnonymousUserPermissions())))
params.isIncludeAnonymousUserPermissions(),
getEntityManager(params))))
.doOnSuccess(params::permissionGroups)
.then();
}
Expand All @@ -550,16 +553,14 @@ private Mono<Void> ensurePermissionGroupsInParams(QueryAllParams<T> params) {
public int updateExecute(QueryAllParams<T> params, BridgeUpdate update) {
Set<String> permissionGroupsSet = params.getPermissionGroups();
ArrayList<String> permissionGroups;

EntityManager em = getEntityManager(params);
if (CollectionUtils.isEmpty(permissionGroupsSet)) {
permissionGroups =
new ArrayList<>(getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser()));
permissionGroups = new ArrayList<>(
getCurrentUserPermissionGroupsIfRequired(params.getPermission(), params.getUser(), em));
} else {
permissionGroups = new ArrayList<>(permissionGroupsSet);
}

final EntityManager em = params.getEntityManager() == null ? getEntityManager() : params.getEntityManager();

final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<T> cq = cb.createQuery(genericDomain);
final CriteriaUpdate<T> cu = cb.createCriteriaUpdate(genericDomain);
Expand Down Expand Up @@ -709,10 +710,10 @@ public int updateFirst(BridgeQuery<T> query, T resource, EntityManager entityMan
return queryBuilder().entityManager(entityManager).criteria(query).updateFirst(resource);
}

public T setUserPermissionsInObject(T obj, User user) {
public T setUserPermissionsInObject(T obj, User user, EntityManager em) {
Set<String> permissionGroups = new HashSet<>();
if (isValidUser(user)) {
permissionGroups = getPermissionGroupsForUser(user);
permissionGroups = getPermissionGroupsForUser(user, em);
}
return setUserPermissionsInObject(obj, permissionGroups);
}
Expand Down Expand Up @@ -748,15 +749,15 @@ public T setUserPermissionsInObject(T obj, Collection<String> permissionGroups)
* 2. Get all the permission groups associated with anonymous user
* 3. Return the set of all the permission groups.
*/
protected Set<String> getAllPermissionGroupsForUser(User user) {
protected Set<String> getAllPermissionGroupsForUser(User user, EntityManager em) {
if (!isValidUser(user)) {
return Collections.emptySet();
} else if (user.getTenantId() == null) {
user.setTenantId(cacheableRepositoryHelper.getDefaultTenantId().block());
}

Set<String> permissionGroups = new HashSet<>(
cacheableRepositoryHelper.getPermissionGroupsOfUser(user).block());
cacheableRepositoryHelper.getPermissionGroupsOfUser(user, em).block());
permissionGroups.addAll(getAnonymousUserPermissionGroups().block());

return permissionGroups;
Expand All @@ -767,15 +768,15 @@ protected Set<String> getAllPermissionGroupsForUser(User user) {
* 2. Get all the permission groups associated with anonymous user
* 3. Return the set of all the permission groups.
*/
protected Set<String> getStrictPermissionGroupsForUser(User user) {
protected Set<String> getStrictPermissionGroupsForUser(User user, EntityManager em) {

if (!isValidUser(user)) {
return Collections.emptySet();
} else if (user.getTenantId() == null) {
String tenantId = cacheableRepositoryHelper.getDefaultTenantId().block();
user.setTenantId(tenantId);
}
return cacheableRepositoryHelper.getPermissionGroupsOfUser(user).block();
return cacheableRepositoryHelper.getPermissionGroupsOfUser(user, em).block();
}

protected Mono<Set<String>> getAnonymousUserPermissionGroups() {
Expand Down Expand Up @@ -886,4 +887,8 @@ private static boolean isValidUser(User user) {
&& StringUtils.hasLength(user.getEmail())
&& (user.isAnonymous() || StringUtils.hasLength(user.getId()));
}

private EntityManager getEntityManager(QueryAllParams<T> params) {
return params.getEntityManager() == null ? entityManager : params.getEntityManager();
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.appsmith.server.repositories.ce;

import com.appsmith.caching.annotations.Cache;
import com.appsmith.server.domains.Tenant;
import com.appsmith.server.domains.User;
import jakarta.persistence.EntityManager;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Set;

public interface CacheableRepositoryHelperCE {

@Cache(cacheName = "permissionGroupsForUser", key = "{#user.email + #user.tenantId}")
Mono<Set<String>> getPermissionGroupsOfUser(User user);

Mono<Set<String>> getPermissionGroupsOfUser(User user, EntityManager entityManager);

Mono<Set<String>> preFillAnonymousUserPermissionGroupIdsCache();

Mono<Set<String>> getPermissionGroupsOfAnonymousUser();
Expand Down
Loading

0 comments on commit 24495f7

Please sign in to comment.