Neo4J repository not wired everywhere - spring

I've defined a Neo4j repository as follow (code is in Kotlin but it's very close to Java) :
#Repository
interface UserRepository : GraphRepository<User> {
fun findByEmail(email: String): User?
#Query("match (n:User)-[:IS_AUTH]->(:Permission {name: {0}}) where id(n) = {1} return n")
fun authorizedUser(permission: String, userId: Long): User?
}
In a controller, I've written:
#Controller
#RequestMapping("/company")
open class CreateCompanyController {
#Autowired private lateinit var userRepo: UserRepository
#RequestMapping(method = arrayOf(RequestMethod.POST))
#ResponseBody
fun createCompany(#RequestParam(value = "name", required = true) name: String,
#RequestParam(value = "siret", required = true) siret: Long) : ResponseEntity<String> {
val connectedUser = SecurityContextHolder.getContext().authentication.principal as User
val testUser = userRepo.findByEmail("test#company.com")
if (!PermissionManager().hasAuthorizationFor(PermissionManager.ADMIN_ALL, connectedUser))
return ResponseEntity("You're not authorized to create a company", HttpStatus.FORBIDDEN)
return ResponseEntity(HttpStatus.OK)
}
}
In the code above, the userRepo property is set well (aka. its value is not null).
I want to have this UserRepository in a custom service. So, I've coded:
#Service
open class PermissionManager {
companion object {
val ADMIN_ALL = "ADMIN_ALL"
}
#Autowired private lateinit var userRepo: UserRepository
fun hasAuthorizationFor(permissionName: String, user: User): Boolean {
return userRepo.authorizedUser(permissionName, user.id!!) != null
}
}
But in this case, the userRepo property is not initialized.
I've written the PersistenceContext class as:
#Configuration
#ComponentScan("persistence")
#EnableNeo4jRepositories("persistence.repositories")
#EnableTransactionManagement
open class PersistenceContext : Neo4jConfiguration() {
#Bean override fun getSessionFactory(): SessionFactory {
return SessionFactory("persistence.domain")
}
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
open override fun getSession(): Session {
return super.getSession()
}
}
What I don't understand is why is the userRepo set in my controller but not in my service ?

Related

How to initialize repository from service in Springboot Kotlin

#Service
public class AvailablePolicyService {
#Autowired
private var availablePolicyRepository : AvailablePolicyRepository = **AvailablePolicyRepository()**
fun saveAvailablePolicy(availablePolicy: AvailablePolicy): AvailablePolicy { return availablePolicyRepository.save(availablePolicy) }
fun getAllAvailablePolicy(): List<AvailablePolicy>{ return availablePolicyRepository.findAll() }
fun getAvailablePolicyByPolicyId(policyId: String?): AvailablePolicy? {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyId().equals(policyId)) {
return availablePolicy
}
}
return null
}
fun getAvailablePolicyByPolicyCategory(policyCategory: String?): ArrayList<AvailablePolicy> {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
var availablePolicyCategory = ArrayList<AvailablePolicy>()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyCategory().equals(policyCategory)) {
availablePolicyCategory.add(availablePolicy)
}
}
return availablePolicyCategory
}
}
#Repository
interface AvailablePolicyRepository : MongoRepository<AvailablePolicy, String>
The bolded text shows where the error is showing and it reads "Interface AvailablePolicyRepository does not have constructors". How do I initialize repository from service?
How do I initialize repository from service?
That's the thing. You don't! Spring does it for you:
#Autowired
private lateinit var availablePolicyRepository: AvailablePolicyRepository
Field injection is rather obsolete and you should consider using contructor injection instead.
#Service
class AvailablePolicyService(private val availablePolicyRepository: AvailablePolicyRepository) {...}

Spring test #Transactional with #Nested junit classes

I am encountering a problem in spring transactions: even though I see in the output that a transaction is created and rolled back after each test method; when I check the database content, it still keeps the document that was saved during the transaction.
I ran mongodb instance in replica mode following instructions from here https://www.mongodb.com/docs/manual/tutorial/deploy-replica-set-for-testing/.
My test class is defined as follows:
#Transactional
#AutoConfigureMockMvc
#ActiveProfiles(profiles = ["test"])
#DisplayName("Login Functionality Test")
#SpringBootTest(classes = [TestConfiguration::class])
class LoginFunctionalityTest #Autowired constructor(
private val mockMvc: MockMvc,
private val userRepository: UserRepository,
private val openUserRepository: OpenUserRepository,
private val patientRepository: PatientRepository,
private val passwordEncoder: PasswordEncoder,
private val jwtMatcher: JWTMatcher,
#Value("\${secret:sD1fRUWtBdfA8BNcbf}") private val fastLoginSecret: String
) {
private val existingUsersPassword = "SOME_PASSWORD"
private val userFastLoginTokenSecret = "ANY_SECRET"
private lateinit var existingUser: User
private lateinit var existingOpenUser: OpenUser
private lateinit var existingPatient: Patient
#BeforeEach
fun prepareContext() {
println("Init db")
existingUser = userRepository.save(User(null, now(), "test", "test", "test#test.test", passwordEncoder.encode(existingUsersPassword), UserAuthority.Patient, true, true))
existingOpenUser = openUserRepository.save(OpenUser(null, "ANY", nextExpiryDate()))
existingPatient = patientRepository.save(Patient(null, existingUser))
}
#DisplayName("Login Controller")
#Nested
inner class LoginControllerTest{
#Test
fun `should return unauthorized 401 status code when user logins with not existing email`() {
val invalidEmail = "INVALID_EMAIL"
assertThrows<EmptyResultDataAccessException> { userRepository.findByEmail(invalidEmail) }
mockMvc.post("/auth/login") {
param("username", invalidEmail)
param("password", existingUsersPassword)
}.andExpect {
jsonPath("$.status", equalToIgnoringCase(HttpStatus.UNAUTHORIZED.name))
jsonPath("$.error",StringContains(true,"Invalid login credentials"))
jsonPath("$.data", nullValue())
status { isUnauthorized() }
}
}
...
}
#ActiveProfiles(profiles = ["open-user"])
#DisplayName("Open User Login Controller")
#Nested
inner class OpenUserControllerLoginTest(){
#Test
fun `should return bad request 400 status when any of the required login parameters are missing`(){
val validFastLoginToken = String(Base64.getEncoder().encode(passwordEncoder.encode("$userFastLoginTokenSecret${existingUser.passwordHash}$fastLoginSecret").toByteArray()))
mockMvc
.post("/auth/login/fast") {
param("userId", existingUser.id!!)
param("secret", userFastLoginTokenSecret)
param("fastLoginToken", validFastLoginToken)
}.andExpect {
jsonPath("$.data.isUserActivated", equalTo(existingUser.activated))
jsonPath("$.data.accessToken",jwtMatcher.matches(existingUser))
jsonPath("$.status", equalToIgnoringCase(HttpStatus.OK.name))
jsonPath("$.error", nullValue())
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
}
}
...
}
The output in console has the "Init db" printed between creation and roll back of transaction.
It works correctly only if I move #ActiveProfiles from #Nested class to the outer class. Seems like #ActiveProfiles causes context reload but I don't know why this causes problems with transactions.

How to initialize variables in parent abstract class of spring bean using Kotlin?

I have the next structure of spring beans
abstract class GenericRepository<T> {
private val FIND_BY_ID_SQL = "SELECT * FROM ${this.getTableName()} WHERE id = ?"
abstract fun getTableName(): String
abstract fun jdbcTemplate(): JdbcTemplate
abstract fun getMapper(): RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate().queryForObject(FIND_BY_ID_SQL, arrayOf(id), getMapper())
}
}
User repository
#Repository
class UserRepository(
#Autowired
private val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>() {
companion object {
private const val INSERT_SQL = "INSERT INTO \"user\"(name, age) VALUES (?,?)"
}
private class LogMapper : RowMapper<User> {
override fun mapRow(rs: ResultSet, rowNum: Int): User? {
return User(
id = rs.getLong("id"),
name = rs.getString("name"),
age = rs.getInt("operation")
)
}
}
override fun getTableName(): String {
return "user"
}
override fun jdbcTemplate(): JdbcTemplate {
return jdbcTemplate
}
override fun getMapper(): RowMapper<User> {
return LogMapper()
}
}
The problem when Spring creates proxy and creates bean of UserRepository it doesn't initialize FIND_BY_ID_SQL leaving it null.
The question: how usign abstract class make spring initialize FIND_BY_ID_SQL variable?
UPD
I used #Component instead of #Repository and the problem was solved. FIND_BY_ID_SQL is not null anymore.
You could work around the problem by making it lazy:
private val FIND_BY_ID_SQL by lazy { "SELECT * FROM ${this.getTableName()} WHERE id = ?" }
However, you should first be sure it's an actual problem (e.g. that when you call find you get an exception), because the proxy might simply delegate to a "real" UserRepository with non-null FIND_BY_ID_SQL (and jdbcTemplate etc.), depending on Spring's internal details.
In addition, you need to be careful when your superclass properties are initialized depending on subclass; I think your exact situation should work, but I'd prefer to write it as
abstract class GenericRepository<T>(val tableName: String) {
private val FIND_BY_ID_SQL = "SELECT * FROM ${tableName} WHERE id = ?"
abstract val jdbcTemplate: JdbcTemplate
abstract val mapper: RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate.queryForObject(FIND_BY_ID_SQL, arrayOf(id), mapper)
}
}
#Repository
class UserRepository(
#Autowired
override val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>("user") { ... }

How can I use a stored procedure MySQL in Spring Boot?

I'm trying to solve a problem: When user click on delete a record and instead of delete it, this record will change status to 'false'.
I suppose to create stored procedures in MySQL like "getAllUser", "changeStatus" but I'm confusing what I have to do.
This is structure of my project:
-configuration
JpaConfiguration
-controller
RestApiController
-model
User
-Repository
UserRepository
-Service
UserService
UserServiceImpl
Then I expect the index page only get records that have status is 'true'.
Procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `getAllUsers`()
BEGIN
SELECT * FROM APP_USER where status = 0;
END
RestApiController
#RestController
#RequestMapping("/api")
public class RestApiController {
#Autowired
UserService userService;
#RequestMapping(value = "/user/", method = RequestMethod.GET)
public List<User> getAllUsers(Boolean status){
Boolean statusWhere = org.apache.commons.lang3.BooleanUtils.isNotTrue(false);
List<User> users = userServiceImpl.getAllUsers(statusWhere);
if (users == null){
return new ArrayList<>();
} else {
return users;
}
}
// Change status
#RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?>deleteUserById(#PathVariable("id") long id){
logger.info("Delete User with id {}", id);
User currentUser = userService.findById(id);
currentUser.setStatus(true);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}
model/User
#Entity
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(
name = "getAllUsers",
procedureName = "getAllUsers",
resultClasses = {User.class},
parameters = {
#StoredProcedureParameter(
mode = ParameterMode.IN,
name = "status",
type = Boolean.class)
}
)
})
service/UserServiceImpl
#Component
#Service("userService")
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private EntityManager entityManager;
#SuppressWarnings("unchecked")
public List<User> getAllUsers(Boolean status){
StoredProcedureQuery storedProcedureQuery = this.entityManager.createNamedStoredProcedureQuery("getAllUsers");
storedProcedureQuery.setParameter("status",status);
storedProcedureQuery.execute();
return storedProcedureQuery.getResultList();
}
public boolean isUserExist(User user) {
return findByName(user.getName()) != null;
}
}

Spring boot - setters on session scoped component not working from singleton service - fields are null

I have a simple service behind a REST controller in Spring Boot. The service is a singleton (by default) and I am autowiring a session-scoped bean component used for storing session preferences information and attempting to populate its values from the service. I call setters on the autowired component, but the fields I am setting stay null and aren't changed.
Have tried with and without Lombok on the bean; also with and without implementing Serializable on FooPref; also copying properties from FooPrefs to another DTO and returning it; also injecting via #Autowired as well as constructor injection with #Inject. The fields stay null in all of those cases.
Running Spring Boot (spring-boot-starter-parent) 1.5.6.RELEASE, Java 8, with the spring-boot-starter-web.
Session-scoped component:
#Component
#SessionScope(proxyMode = ScopedProxyMode.TARGET_CLASS)
#Data
#NoArgsConstructor
public class FooPrefs implements Serializable {
private String errorMessage;
private String email;
private String firstName;
private String lastName;
}
REST Controller:
#RestController
#RequestMapping("/api/foo")
public class FooController {
#Autowired
private FooPrefs fooPrefs;
private final FooService fooService;
#Inject
public FooController(FooService fooService) {
this.fooService = fooService;
}
#PostMapping(value = "/prefs", consumes = "application/json", produces = "application/json")
public FooPrefs updatePrefs(#RequestBody Person person) {
fooService.updatePrefs(person);
// These checks are evaluating to true
if (fooPrefs.getEmail() == null) {
LOGGER.error("Email is null!!");
}
if (fooPrefs.getFirstName() == null) {
LOGGER.error("First Name is null!!");
}
if (fooPrefs.getFirstName() == null) {
LOGGER.error("First Name is null!!");
}
return fooPrefs;
}
}
Service:
#Service
#Scope(value = "singleton")
#Transactional(readOnly = true)
public class FooService {
#Autowired
private FooPrefs fooPrefs;
#Inject
public FooService(FooRepository fooRepository) {
this.fooRepository = fooRepository;
}
public void updatePrefs(Person person) {
fooRepository.updatePerson(person);
//the fields below appear to getting set correctly while debugging in the scope of this method call but after method return, all values on fooPrefs are null
fooPrefs.setEmail(person.getEmail());
fooPrefs.setFirstName(person.getFirstName());
fooPrefs.setLastName(person.getLastName());
}
}
I discovered my problem. Fields were being added to my FooPrefs session-managed object and were breaking my client. The setters were actually working and being nulled out by some error handling code.
Edits per below fixed the JSON serialization problems:
Session-scoped component (no change)
New Dto
#Data
#NoArgsConstructor
public class FooPrefsDto {
private String errorMessage;
private String email;
private String firstName;
private String lastName;
}
Controller (updated)
#RestController
#RequestMapping("/api/foo")
public class FooController {
private final FooService fooService;
#Inject
public FooController(FooService fooService) {
this.fooService = fooService;
}
#PostMapping(value = "/prefs", consumes = "application/json", produces = "application/json")
public FooPrefsDto updatePrefs(#RequestBody Person person) {
FooPrefsDto result = fooService.updatePrefs(person);
// results coming back correctly now
if (result.getEmail() == null) {
LOGGER.error("Email is null!!");
}
if (result.getFirstName() == null) {
LOGGER.error("First Name is null!!");
}
if (result.getFirstName() == null) {
LOGGER.error("First Name is null!!");
}
return result;
}
}
Service (updated)
#Service
#Scope(value = "singleton")
#Transactional(readOnly = true)
public class FooService {
#Autowired
private FooPrefs fooPrefs;
#Inject
public FooService(FooRepository fooRepository) {
this.fooRepository = fooRepository;
}
public FooPrefsDto updatePrefs(Person person) {
fooRepository.updatePerson(person);
//the fields below appear to getting set correctly while debugging in the scope of this method call but after method return, all values on fooPrefs are null
fooPrefs.setEmail(person.getEmail());
fooPrefs.setFirstName(person.getFirstName());
fooPrefs.setLastName(person.getLastName());
return getFooPrefsDto();
}
private FooPrefsDto getFooPrefsDto() {
FooPrefsDto retDto = new FooPrefsDto();
retDto.setEmail(fooPrefs.getEmail());
retDto.setLastName(fooPrefs.getLastName());
retDto.setFirstName(fooPrefs.getFirstName());
return retDto;
}
}

Resources