UserDataManagerAdapter.java
package com.wilzwert.myjobs.infrastructure.persistence.mongo.service;
import com.mongodb.bulk.BulkWriteResult;
import com.wilzwert.myjobs.core.domain.model.job.Job;
import com.wilzwert.myjobs.core.domain.model.job.JobState;
import com.wilzwert.myjobs.core.domain.model.user.User;
import com.wilzwert.myjobs.core.domain.model.user.UserId;
import com.wilzwert.myjobs.core.domain.model.user.UserView;
import com.wilzwert.myjobs.core.domain.model.user.ports.driven.UserDataManager;
import com.wilzwert.myjobs.core.domain.shared.bulk.BulkDataSaveResult;
import com.wilzwert.myjobs.core.domain.shared.specification.DomainSpecification;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.entity.MongoJob;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.entity.MongoUser;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.mapper.JobMapper;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.mapper.UserMapper;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.repository.MongoJobRepository;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.repository.MongoRefreshTokenRepository;
import com.wilzwert.myjobs.infrastructure.persistence.mongo.repository.MongoUserRepository;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.mongodb.core.BulkOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Wilhelm Zwertvaegher
*/
@Component
public class UserDataManagerAdapter implements UserDataManager {
private final MongoUserRepository mongoUserRepository;
private final MongoJobRepository mongoJobRepository;
private final AggregationService aggregationService;
private final UserMapper userMapper;
private final JobMapper jobMapper;
private final MongoRefreshTokenRepository mongoRefreshTokenRepository;
private final MongoTemplate mongoTemplate;
public UserDataManagerAdapter(final MongoUserRepository mongoUserRepository, final MongoJobRepository mongoJobRepository, final AggregationService aggregationService, final UserMapper userMapper, JobMapper jobMapper, MongoRefreshTokenRepository mongoRefreshTokenRepository, MongoTemplate mongoTemplate) {
this.mongoUserRepository = mongoUserRepository;
this.mongoJobRepository = mongoJobRepository;
this.aggregationService = aggregationService;
this.userMapper = userMapper;
this.jobMapper = jobMapper;
this.mongoRefreshTokenRepository = mongoRefreshTokenRepository;
this.mongoTemplate = mongoTemplate;
}
@Override
public List<UserView> findView(DomainSpecification specifications) {
Aggregation aggregation = aggregationService.createAggregation(specifications);
return userMapper.toDomainView(aggregationService.aggregate(aggregation, "users", MongoUser.class));
}
@Override
public Map<UserId, User> findMinimal(DomainSpecification specifications) {
Aggregation aggregation = aggregationService.createAggregation(specifications);
return userMapper.toDomain(aggregationService.aggregate(aggregation, "users", MongoUser.class))
.stream()
.collect(Collectors.toMap(User::getId, user -> user));
}
@Override
public Optional<User> findByEmail(String email) {
return getFullUser(mongoUserRepository.findByEmail(email));
}
@Override
public Optional<User> findMinimalByEmailValidationCode(String code) {
return mongoUserRepository.findByEmailValidationCode(code).map(userMapper::toDomain);
}
@Override
public Optional<User> findByResetPasswordToken(String token) {
return getFullUser(mongoUserRepository.findByResetPasswordToken(token));
}
@Override
public Optional<User> findByUsername(String username) {
return getFullUser(mongoUserRepository.findByUsername(username));
}
@Override
public Optional<User> findByEmailOrUsername(String email, String username) {
return getFullUser(mongoUserRepository.findByEmailOrUsername(email, username));
}
@Override
public Optional<UserView> findViewById(UserId id) {
return mongoUserRepository.findById(id.value()).map(userMapper::toDomainView);
}
@Override
public Optional<User> findMinimalByUsername(String username) {
return mongoUserRepository.findByUsername(username).map(userMapper::toDomain);
}
@Override
public Optional<User> findMinimalByEmail(String email) {
return mongoUserRepository.findByEmail(email).map(userMapper::toDomain).or(Optional::empty);
}
private Optional<User> getFullUser(Optional<MongoUser> user) {
return user.map(u -> userMapper.toDomain(u).completeWith(jobMapper.toDomain(mongoJobRepository.findByUserId(u.getId()))));
}
@Override
public Optional<User> findById(UserId id) {
return getFullUser(mongoUserRepository.findById(id.value()));
}
@Override
public Optional<User> findMinimalById(UserId id) {
return mongoUserRepository.findById(id.value()).map(userMapper::toDomain);
}
/**
* Saves a User, and updates their username and email in the lookup cache
* @param user the User to save
* @return the saved User
*/
@Override
@Caching(
evict = {
@CacheEvict(value = "emailExists", key = "#user.email"),
@CacheEvict(value = "usernameExists", key = "#user.username")
}
)
public User save(User user) {
return userMapper.toDomain(mongoUserRepository.save(this.userMapper.toEntity(user)));
}
@Override
@Transactional
public User saveUserAndJob(User user, Job job) {
mongoJobRepository.save(this.jobMapper.toEntity(job));
return save(user);
}
@Override
@Transactional
public User deleteJobAndSaveUser(User user, Job job) {
mongoJobRepository.delete(this.jobMapper.toEntity(job));
return userMapper.toDomain(this.mongoUserRepository.save(this.userMapper.toEntity(user)));
}
@Override
@Cacheable(value = "emailExists", key = "#email")
public boolean emailExists(String email) {
return findByEmail(email).isPresent();
}
@Override
@Cacheable(value = "usernameExists", key = "#username")
public boolean usernameExists(String username) {
return findByUsername(username).isPresent();
}
/**
* Deletes a user, and deletes their username and email from the lookup cache
* @param user the User to delete
*/
@Override
@Caching(
evict = {
@CacheEvict(value = "emailExists", key = "#user.email"),
@CacheEvict(value = "usernameExists", key = "#user.username")
}
)
@Transactional
public void deleteUser(User user) {
mongoJobRepository.deleteByUserId(user.getId().value());
mongoRefreshTokenRepository.deleteByUserId(user.getId().value());
mongoUserRepository.delete(userMapper.toEntity(user));
}
@Override
public BulkDataSaveResult saveAll(Set<User> users) {
// we chose to throw an exception because it seems like something went wrong if someone tries to save an empty set
if(users.isEmpty()) {
throw new IllegalArgumentException("users must not be empty");
}
BulkOperations bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED, MongoUser.class);
List<MongoUser> mongoUsers = userMapper.toEntity(users.stream().toList());
for(MongoUser user : mongoUsers) {
Update update = new Update();
update.set("jobFollowUpReminderSentAt", user.getJobFollowUpReminderSentAt());
bulkOps.updateOne(Query.query(Criteria.where("_id").is(user.getId())), update);
}
BulkWriteResult result = bulkOps.execute();
return new BulkDataSaveResult(users.size(), result.getModifiedCount(), result.getInsertedCount(), result.getDeletedCount());
}
@Override
public List<JobState> getJobsState(User user) {
Query query = new Query();
query.addCriteria(Criteria.where("userId").is(user.getId().value()));
query.fields().include("status").include("updatedAt").include("statusUpdatedAt");
List<MongoJob> projection = mongoTemplate.find(query, MongoJob.class);
return projection.stream().map(j -> new JobState(j.getStatus(), j.getUpdatedAt(), j.getStatusUpdatedAt())).collect(Collectors.toList());
}
}