I have an entity class that refers to itself as the parent class.
#NodeEntity
public class Config {
#Id
#GeneratedValue
private Long id;
#Relationship(type = "OVERRIDES", direction="INCOMING")
private Config parentConfig;
#Properties(allowCast=true, prefix="properties", delimiter=".")
private Map<String, String> properties;
....
}
When I run the built-in findById() method in my java code, the "parentConfig" is always the same as the child object. I never see the other end of the relationship. What am I doing wrong?
this works as expected but depends on how you want to model your config.
Assuming you want to express "Child overwrites parent config", which I would model as
CREATE (c:Config {name: 'Parent'}) <- [:OVERRIDES] - (c2:Config {name: 'Child'})
This gives you:
Then you must model the association as outgoing from the child object like this:
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.Relationship;
public class Config {
#Id
#GeneratedValue
private Long id;
private String name;
#Relationship(type = "OVERRIDES", direction = Relationship.OUTGOING)
private Config parent;
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Config getParent() {
return parent;
}
#Override public String toString() {
return "Config{" +
"name='" + name + '\'' +
'}';
}
}
Turning this into a full example:
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Component;
interface ConfigRepo extends Neo4jRepository<Config, Long> {
Optional<Config> findOneByName(String name);
}
#Component
class Example implements CommandLineRunner {
#Autowired
private ConfigRepo configRepo;
#Override
public void run(String... args) throws Exception {
configRepo.findOneByName("Parent").ifPresent(c -> {
System.out.println("Config " + c + " has parent " + c.getParent());
});
configRepo.findOneByName("Child").ifPresent(c -> {
System.out.println("Config " + c + " has parent " + c.getParent());
});
}
}
#SpringBootApplication
public class SorecassocApplication {
public static void main(String[] args) {
SpringApplication.run(SorecassocApplication.class, args);
}
}
Will return
2018-10-10 09:20:21.960 INFO 3832 --- [ main] o.n.o.drivers.bolt.request.BoltRequest : Request: MATCH (n:`Config`) WHERE n.`name` = { `name_0` } WITH n RETURN n,[ [ (n)-[r_o1:`OVERRIDES`]->(c1:`Config`) | [ r_o1, c1 ] ] ], ID(n) with params {name_0=Parent}
Config Config{name='Parent'} has parent null
2018-10-10 09:20:21.984 INFO 3832 --- [ main] o.n.o.drivers.bolt.request.BoltRequest : Request: MATCH (n:`Config`) WHERE n.`name` = { `name_0` } WITH n RETURN n,[ [ (n)-[r_o1:`OVERRIDES`]->(c1:`Config`) | [ r_o1, c1 ] ] ], ID(n) with params {name_0=Child}
Config Config{name='Child'} has parent Config{name='Parent'}
If you model the relationship as INCOMING from the perspective of the child, than you have to inverse the relation ship in the nodes. Otherwise the output is as:
2018-10-10 09:22:54.929 INFO 3837 --- [ main] o.n.o.drivers.bolt.request.BoltRequest : Request: MATCH (n:`Config`) WHERE n.`name` = { `name_0` } WITH n RETURN n,[ [ (n)<-[r_o1:`OVERRIDES`]-(c1:`Config`) | [ r_o1, c1 ] ] ], ID(n) with params {name_0=Parent}
Config Config{name='Parent'} has parent Config{name='Child'}
2018-10-10 09:22:54.951 INFO 3837 --- [ main] o.n.o.drivers.bolt.request.BoltRequest : Request: MATCH (n:`Config`) WHERE n.`name` = { `name_0` } WITH n RETURN n,[ [ (n)<-[r_o1:`OVERRIDES`]-(c1:`Config`) | [ r_o1, c1 ] ] ], ID(n) with params {name_0=Child}
Config Config{name='Child'} has parent null
Here are my dependencies to turn the examples into a running application:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>sorecassoc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>sorecassoc</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Please let me know if this solves your problem.
Related
In the OpenApi30Config configuration file I have:
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
#Configuration
#OpenAPIDefinition(info = #Info(title = "Users API", version = "2.0", description = "Users Information"))
#SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, bearerFormat = "JWT", scheme = "bearer")
public class OpenApi30Config {
}
In the WebSecurityConfig
#EnableWebSecurity
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment environment;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterAfter(new JWTAuthorizationFilter(environment), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/auth").permitAll()
.antMatchers("/open-api/**").permitAll()
.anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("someUser")
.password(passwordEncoder().encode("somePassword"))
.authorities("ADMIN");
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Controllers:
AuthController
#RestController
public class AuthController implements EnvironmentAware {
private Environment environment;
#PostMapping("auth")
public User login(#RequestParam("user") String name, #RequestParam("password") String pwd) {
String token = getJWTToken(name);
User user = new User();
user.setName(name);
user.setToken(token);
return user;
}
private String getJWTToken(String username) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
String token = Jwts
.builder()
.setId("softtekJWT")
.setSubject(username)
.claim("authorities",
grantedAuthorities.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()))
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + Long.parseLong(environment.getProperty("millisecond.expiration"))))
.signWith(SignatureAlgorithm.HS512,
environment.getProperty("secret.key").getBytes()).compact();
return "Bearer " + token;
}
#Override
public void setEnvironment(final Environment environment) {
this.environment = environment;
}
}
The UserController:
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
#RestController
#RequestMapping("/api/users")
public class UserController {
#Autowired
private UserServiceIface userService;
#Autowired UserPasswordValidator userPasswordValidator;
#Operation(summary = "list Users", security = #SecurityRequirement(name = "bearerAuth"))
#GetMapping
public ResponseEntity<?> list(){
return ResponseEntity.ok().body(userService.findAll());
}
#Operation(summary = "view User", security = #SecurityRequirement(name = "bearerAuth"))
#GetMapping("/{id}")
public ResponseEntity<?> view(#PathVariable Long id) {
Optional<User> optionalStoredUser = userService.findById(id);
if (!optionalStoredUser.isPresent()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok().body(optionalStoredUser.get());
}
#Operation(summary = "create User", security = #SecurityRequirement(name = "bearerAuth"))
#PostMapping
public ResponseEntity<?> create(#RequestHeader(value="Authorization") String token,
#Valid #RequestBody User user, BindingResult result) {
userPasswordValidator.validate(user, result);
throwExceptionIfErrors(result);
Optional<User> optionalStoredUser = userService.findByEmail(user.getEmail());
if (optionalStoredUser.isPresent()) {
throw new ExistingMailException(user.getEmail());
}
user.setIsactive(true);
user.setToken(token);
User createdUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
}
#Operation(summary = "edit User", security = #SecurityRequirement(name = "bearerAuth"))
#PutMapping("/{id}")
public ResponseEntity<?> edit(#RequestHeader(value="Authorization") String token,
#Valid #RequestBody User user, BindingResult result, #PathVariable Long id) {
userPasswordValidator.validate(user, result);
throwExceptionIfErrors(result);
if (user.getEmail() != null && !user.getEmail().isEmpty()) {
boolean emailUsed = userService.findByEmailAndIdNot(user.getEmail(), id).size() > 0;
if (emailUsed) {
throw new ExistingMailException(user.getEmail());
}
}
Optional<User> optionalStoredUser = userService.findById(id);
if (!optionalStoredUser.isPresent()) {
return ResponseEntity.notFound().build();
}
User editedUser = optionalStoredUser.get();
editedUser.setName(user.getName());
editedUser.setEmail(user.getEmail());
editedUser.setPassword(user.getPassword());
editedUser.setPhones(user.getPhones());
editedUser.setModified(new Date());
editedUser.setIsactive(user.isIsactive());
editedUser.setToken(token);
try {
User updatedUser = userService.save(editedUser);
return ResponseEntity.status(HttpStatus.CREATED).body(updatedUser);
} catch (Exception exp) {
throw new DefaultException(exp.getLocalizedMessage());
}
}
#Operation(summary = "delete User", security = #SecurityRequirement(name = "bearerAuth"))
#DeleteMapping("/{id}")
public ResponseEntity<?> delete(#PathVariable Long id) {
userService.deleteById(id);
return ResponseEntity.noContent().build();
}
private void throwExceptionIfErrors(BindingResult result) {
if (result.hasErrors()) {
FieldError passwordFieldError = result.getAllErrors()
.stream().map(e -> (FieldError) e)
.filter(f -> f.getField().equals("password"))
.findFirst().orElse(null);
if (passwordFieldError != null) {
throw new PatternPasswordException(passwordFieldError.getDefaultMessage());
}
FieldError emailFieldError = result.getAllErrors()
.stream().map(e -> (FieldError) e)
.filter(f -> f.getField().equals("email"))
.findFirst().orElse(null);
if (emailFieldError != null) {
throw new PatternEmailException(emailFieldError.getDefaultMessage());
}
}
}
}
In my pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<!-- <version>5.8.2</version> -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<!-- <version>4.2.0</version> -->
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<!-- <version>4.2.0</version> -->
</dependency>
</dependencies>
Now UserControllerTest in the test:
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#WebMvcTest(UserController.class)
class UserControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private UserServiceIface userService;
#MockBean
UserPasswordValidator userPasswordValidator;
private List<User> users;
#SuppressWarnings("serial")
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
users = new ArrayList<User>() {
{
User user = new User();
user.setId(1L);
user.setName("First");
user.setEmail("i#country.com");
user.setPassword("abc1");
add(user);
user = new User();
user.setId(2L);
user.setName("Second");
user.setEmail("you#country.com");
user.setPassword("abc2");
add(user);
user = new User();
user.setId(3L);
user.setName("Third");
user.setEmail("he#country.com");
user.setPassword("abc3");
add(user);
}
};
}
#Test
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].name").value("First"))
;
}
Running I get:
MockHttpServletResponse:
Status = 403
Error message = Access Denied
Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
EDIT 1:
I changed my test class!
package org.bz.ms.app.usuarios.controllers;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.jupiter.api.*;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#WebMvcTest(UserController.class)
class UserControllerTest {
#Autowired
private WebApplicationContext context;
#Autowired
private MockMvc mvc;
#MockBean
private UserServiceIface userService;
#MockBean
UserPasswordValidator userPasswordValidator;
private List<User> users;
#SuppressWarnings("serial")
#BeforeEach
void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).build();
// userDao = mock(UserDao.class); // Manually
MockitoAnnotations.openMocks(this);
users = new ArrayList<User>() {
{
User user = new User();
user.setId(1L);
user.setName("First");
user.setEmail("i#country.com");
user.setPassword("abc1");
add(user);
user = new User();
user.setId(2L);
user.setName("Second");
user.setEmail("you#country.com");
user.setPassword("abc2");
add(user);
user = new User();
user.setId(3L);
user.setName("Third");
user.setEmail("he#country.com");
user.setPassword("abc3");
add(user);
}
};
}
#Test
#WithMockUser
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
// .andExpect(content().contentType(MediaType.APPLICATION_JSON))
// .andExpect(jsonPath("$.[0].name").value("First"))
;
}
}
I have doubts about this injection, but I think that I have: WebSecurityConfig and OpenApi30Config.
#Autowired
private WebApplicationContext context;
and this on setUp method:
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).apply(springSecurity()).build();
How to test my controller including the security?
It is because you configure the API can only be accessed by an authenticated user but now you are sending a request as an anonymous user and hence it return 403 access denied error.
The most simplest way is to add #WithMockUser to the test method. It will emulate the test are running with an authenticated user . It also allows to configure what the authorities or role do this user has :
#Test
#WithMockUser
void testList() throws Exception {
// given
when(userService.findAll()).thenReturn(users);
// then
mvc.perform(MockMvcRequestBuilders.get("/api/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[0].name").value("First"))
;
}
Please note that #WithMockUser requires to add the spring-security-test dependency :
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
I currently have a Spring Boot Application configured to use spring-boot-starter-amqp 2.1.5.RELEASE. I have configured it for retries in the yaml:
rabbitmq:
listener:
simple:
retry:
enabled: true # retrys enabled
max-attempts: 3 # total number number attempts (includes the original one)
multiplier: 1.5 # multiple of initial interval for subsequent retries
initial-interval: 1000 # first interval between attempts
In my listener under an certain scenario I throw a AmqpRejectAndDontRequeueException but this does not prevent the re-queueing.
How can I configure a configuration bean inline with the automatic spring configuration to stop further requeue of a message if this exception is thrown?
Queue A should attempt the processing on the queueA listener 3 times and the logs support this.
Queue B should attempt only once and stop when the AmqpRejectAndDontRequeueException is thrown.
SpringBoot Application class:
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import lombok.extern.slf4j.Slf4j;
#SpringBootApplication
#Slf4j
public class AmqpApplication
{
protected static final String X_ATTEMPTS_HEADER = "x-attempts";
protected static final String X_LAST_ATTEMPT_DATE_HEADER = "x-last-attempt-date";
public static void main(String[] args) throws InterruptedException
{
ConfigurableApplicationContext context = SpringApplication.run(AmqpApplication.class, args);
context.close();
System.exit(0);
}
private String host = "localhost";
private Integer port = 5672;
private String vhost = "/";
private String username = "guest";
private String password = "guest";
private String exchangeName = "common-exchange";
#Autowired
private RabbitTemplate rabbitTemplate;
/**
* Configures the connection factory using the configured values
*
* #return Connection factory to use to connect to rabbitmq and send events
**/
private ConnectionFactory connectionFactory()
{
CachingConnectionFactory factory = new CachingConnectionFactory(host, port);
factory.setRequestedHeartBeat(30);
factory.setConnectionTimeout(30000);
factory.setChannelCacheSize(10);
factory.setVirtualHost(vhost);
factory.setUsername(username);
factory.setPassword(password);
return factory;
}
#Bean
public Queue queueA()
{
return QueueBuilder.durable("a").withArgument("x-dead-letter-exchange", "a")
.withArgument("x-dead-letter-routing-key", "a-dead-letter").build();
}
#Bean
public Queue queueB()
{
return QueueBuilder.durable("b").withArgument("x-dead-letter-exchange", "b")
.withArgument("x-dead-letter-routing-key", "b-dead-letter").build();
}
#Bean
Queue DeadLetterQueueA()
{
return QueueBuilder.durable("a-dead-letter").build();
}
#Bean
Queue DeadLetterQueueB()
{
return QueueBuilder.durable("b-dead-letter").build();
}
/**
* Required for executing adminstration functions against an AMQP Broker
*/
#Bean
public AmqpAdmin amqpAdmin(RabbitListenerEndpointRegistry registry)
{
//#// #formatter:off
RabbitAdmin admin = new RabbitAdmin(connectionFactory());
admin.declareQueue(queueA());
admin.declareQueue(queueB());
registry.start();
return admin;
}
/**
* The following is a complete declaration of an exchange, a queue and a
* exchange-queue binding
*/
#Bean
public DirectExchange directExchange()
{
return new DirectExchange(exchangeName, true, false);
}
#Bean
public List<Binding> exchangeBinding()
{
// Important part is the routing key -- this is just an example
return Arrays.asList(
BindingBuilder.bind(queueA()).to(directExchange()).with("a"),
BindingBuilder.bind(DeadLetterQueueA()).to(directExchange())
.with("a"),
BindingBuilder.bind(queueB()).to(directExchange()).with("b"),
BindingBuilder.bind(DeadLetterQueueB()).to(directExchange())
.with("b"));
}
#Bean
public RabbitTemplate rabbitTemplate()
{
// Add the object mapper to the converter
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonOrgModule());
// Add the object mapper to the converter
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
template.setExchange(exchangeName);
return template;
}
#PostConstruct
public void sendMessages() throws InterruptedException
{
rabbitTemplate.convertAndSend(exchangeName, "a", new BeanObject().setName("a"));
rabbitTemplate.convertAndSend(exchangeName, "b", new BeanObject().setName("b"));
}
#RabbitListener(queues = "a")
public void aListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new RuntimeException();
}
#RabbitListener(queues = "b")
public void bListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new AmqpRejectAndDontRequeueException("");
}
private void beforeProcessing(BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attemptNo)
{
//// #formatter:off
attemptNo++;// Increment
message.getMessageProperties().getHeaders().put(X_ATTEMPTS_HEADER, attemptNo);//update attempts header
// #formatter:on
log.info(
"bean: {}, attemptNo: {}",
payload, attemptNo);
}
}
MessageConverter Class:
import java.io.IOException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
#Component
public class MessageConverter implements org.springframework.amqp.support.converter.MessageConverter
{
private final ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
#Override
public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException
{
return null;
}
#Override
public Object fromMessage(Message message) throws MessageConversionException
{
if (message.getMessageProperties() == null || message.getMessageProperties().getHeaders() == null
|| !message.getMessageProperties().getHeaders().containsKey("__TypeId__"))
{
throw new MessageConversionException(
"No header exists in the message for [__TypeId__]. This is required to hint the conversion type.");
}
String typeId = message.getMessageProperties().getHeaders().get("__TypeId__").toString();
try
{
return objectMapper.readValue(message.getBody(), Class.forName(typeId));
}
catch (ClassNotFoundException | IOException e)
{
throw new MessageConversionException(
String.format("Unable to convert message payload to type [%s]", typeId));
}
}
}
Lombok Bean class:
package com.amqp;
import lombok.Data;
import lombok.experimental.Accessors;
#Data
#Accessors(chain = true)
public class BeanObject
{
private String name;
}
POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.amqp</groupId>
<artifactId>amqp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>amqp</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml/jackson-module-json-org -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-json-org -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
</dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Logs:
bean: BeanObject(name=a), attemptNo: 1
bean: BeanObject(name=b), attemptNo: 1
bean: BeanObject(name=a), attemptNo: 2
bean: BeanObject(name=b), attemptNo: 2
bean: BeanObject(name=b), attemptNo: 3
bean: BeanObject(name=a), attemptNo: 3
I found the solution. Amqp wraps the exception thrown inside a ListenerExecutionFailedException. I have overridden the SimpleRabbitListenerContainerFactory and specified my own Retry Policy which extends the SimpleRetryPolicy. I then make sure I pass the cause of the throwable to the retryForException method. I have also made sure that I specify a retryable map of classes in the advice chain:
Here are the logs now, as you can see 'a' attempts 3 times and 'b' only once:
bean: BeanObject(name=b), attemptNo: 1
bean: BeanObject(name=a), attemptNo: 1
bean: BeanObject(name=a), attemptNo: 2
bean: BeanObject(name=a), attemptNo: 3
Here is the new Main Spring boot class:
package com.amqp;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.amqp.AmqpRejectAndDontRequeueException;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.config.RetryInterceptorBuilder;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RejectAndDontRequeueRecoverer;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.classify.BinaryExceptionClassifier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.retry.RetryContext;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.util.ErrorHandler;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import lombok.extern.slf4j.Slf4j;
#SpringBootApplication
#Slf4j
public class AmqpApplication
{
protected static final String X_ATTEMPTS_HEADER = "x-attempts";
protected static final String X_LAST_ATTEMPT_DATE_HEADER = "x-last-attempt-date";
public static void main(String[] args) throws InterruptedException
{
ConfigurableApplicationContext context = SpringApplication.run(AmqpApplication.class, args);
context.close();
System.exit(0);
}
private String host = "localhost";
private Integer port = 5672;
private String vhost = "/";
private String username = "guest";
private String password = "guest";
private String exchangeName = "common-exchange";
#Autowired
private RabbitTemplate rabbitTemplate;
#Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory()
{
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory());
factory.setAdviceChain(retryOperationsInterceptor().build());
factory.setErrorHandler(new ConditionalRejectingErrorHandler());
factory.setAutoStartup(true);
factory.setMessageConverter(new MessageConverter());
return factory;
}
/**
* Configures the connection factory using the configured values
*
* #return Connection factory to use to connect to rabbitmq and send events
**/
private ConnectionFactory connectionFactory()
{
CachingConnectionFactory factory = new CachingConnectionFactory(host, port);
factory.setRequestedHeartBeat(30);
factory.setConnectionTimeout(30000);
factory.setChannelCacheSize(10);
factory.setVirtualHost(vhost);
factory.setUsername(username);
factory.setPassword(password);
return factory;
}
#Bean
public RetryInterceptorBuilder<?> retryOperationsInterceptor()
{
RetryInterceptorBuilder<?> builder = RetryInterceptorBuilder.stateless();
builder.retryPolicy(new MyRetryPolicy(3, retryableClassifier()));
builder.backOffPolicy(backoffPolicy());
MessageRecoverer recoverer = new RejectAndDontRequeueRecoverer();
builder.recoverer(recoverer);
return builder;
}
#Bean
public BackOffPolicy backoffPolicy()
{
ExponentialBackOffPolicy backoffPolicy = new ExponentialBackOffPolicy();
backoffPolicy.setInitialInterval(1000);
backoffPolicy.setMaxInterval(10000);
backoffPolicy.setMultiplier(1.5);
return backoffPolicy;
}
#Bean
public Map<Class<? extends Throwable>, Boolean> retryableClassifier()
{
Map<Class<? extends Throwable>, Boolean> retryableClassifier = new HashMap<>();
retryableClassifier.put(AmqpRejectAndDontRequeueException.class, false);
retryableClassifier.put(Exception.class, true);
return retryableClassifier;
}
#Bean
public Queue queueA()
{
return QueueBuilder.durable("a").withArgument("x-dead-letter-exchange", "a")
.withArgument("x-dead-letter-routing-key", "a-dead-letter").build();
}
#Bean
public Queue queueB()
{
return QueueBuilder.durable("b").withArgument("x-dead-letter-exchange", "b")
.withArgument("x-dead-letter-routing-key", "b-dead-letter").build();
}
#Bean
Queue DeadLetterQueueA()
{
return QueueBuilder.durable("a-dead-letter").build();
}
#Bean
Queue DeadLetterQueueB()
{
return QueueBuilder.durable("b-dead-letter").build();
}
/**
* Required for executing adminstration functions against an AMQP Broker
*/
#Bean
public AmqpAdmin amqpAdmin(RabbitListenerEndpointRegistry registry,
SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory)
{
//#// #formatter:off
RabbitAdmin admin = new RabbitAdmin(connectionFactory());
admin.declareQueue(queueA());
admin.declareQueue(queueB());
registry.start();
return admin;
}
/**
* The following is a complete declaration of an exchange, a queue and a
* exchange-queue binding
*/
#Bean
public DirectExchange directExchange()
{
return new DirectExchange(exchangeName, true, false);
}
#Bean
public List<Binding> exchangeBinding()
{
// Important part is the routing key -- this is just an example
return Arrays.asList(
BindingBuilder.bind(queueA()).to(directExchange()).with("a"),
BindingBuilder.bind(DeadLetterQueueA()).to(directExchange())
.with("a"),
BindingBuilder.bind(queueB()).to(directExchange()).with("b"),
BindingBuilder.bind(DeadLetterQueueB()).to(directExchange())
.with("b"));
}
#Bean
public RabbitTemplate rabbitTemplate()
{
// Add the object mapper to the converter
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JsonOrgModule());
// Add the object mapper to the converter
RabbitTemplate template = new RabbitTemplate(connectionFactory());
template.setMessageConverter(new Jackson2JsonMessageConverter(objectMapper));
template.setExchange(exchangeName);
return template;
}
#PostConstruct
public void sendMessages() throws InterruptedException
{
rabbitTemplate.convertAndSend(exchangeName, "a", new BeanObject().setName("a"));
rabbitTemplate.convertAndSend(exchangeName, "b", new BeanObject().setName("b"));
}
#RabbitListener(queues = "a")
public void aListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new RuntimeException();
}
#RabbitListener(queues = "b")
public void bListener(#Payload BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attempts)
{
beforeProcessing(payload,message,attempts);
throw new AmqpRejectAndDontRequeueException("");
}
private void beforeProcessing(BeanObject payload, Message message,
#Header(required = false, name = X_ATTEMPTS_HEADER, defaultValue = "0") Integer attemptNo)
{
//// #formatter:off
attemptNo++;// Increment
message.getMessageProperties().getHeaders().put(X_ATTEMPTS_HEADER, attemptNo);//update attempts header
// #formatter:on
log.info(
"bean: {}, attemptNo: {}",
payload, attemptNo);
}
private static class MyRetryPolicy extends SimpleRetryPolicy
{
private BinaryExceptionClassifier retryableClassifier;
private int maxAttempts;
#Override
public boolean canRetry(RetryContext context)
{
Throwable t = context.getLastThrowable();
return (t == null || retryForException(t.getCause())) && context.getRetryCount() < maxAttempts;
}
public MyRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions)
{
this.maxAttempts = maxAttempts;
this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions, false);
}
private boolean retryForException(Throwable ex)
{
return this.retryableClassifier.classify(ex);
}
}
public static class MyErrorHandler implements ErrorHandler
{
#Override
public void handleError(Throwable t)
{
if (!this.causeChainContainsARADRE(t))
{
throw new AmqpRejectAndDontRequeueException("Error Handler converted exception to fatal", t);
}
}
private boolean causeChainContainsARADRE(Throwable t)
{
Throwable cause = t.getCause();
while (cause != null)
{
if (cause instanceof AmqpRejectAndDontRequeueException)
{
return true;
}
cause = cause.getCause();
}
return false;
}
}
}
{
"id": 109433,
"name": "test",
"value": [
"c"
]
}
Having the node above I execute this post to modify my object so the value contains "a","b" instead of "c":
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d ' {
"id": 109433,
"name": "test",
"value": [
"a","b"
]
}' 'http://localhost:8080/configs'
Then I execute a get to get all nodes
curl -X GET --header 'Accept: application/json' 'http://localhost:8080/configs'
For some weird reason it will return
{
"id": 109433,
"name": "test",
"value": [
"a",
"b",
"c"
]
}
If I go to neo4j outside my web application and query it I will get
{
"id": 109433,
"name": "test",
"value": [
"a","b"
]
}
So just to be sure I have some sort of data pollution like a cache running somewhere I stop the server and turn it on again, then I do the get again
curl -X GET --header 'Accept: application/json' 'http://localhost:8080/configs'
returns:
{
"id": 109433,
"name": "test",
"value": [
"a",
"b",
]
}
So now "c" is nowhere to be seen.
Here is the code I'm using
api
package io.swagger.api;
import io.swagger.annotations.*;
import io.swagger.model.Config;
import io.swagger.service.ConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
#RestController
#RequestMapping(value = "/configs", produces = {APPLICATION_JSON_VALUE})
#Api(value = "/configs", description = "the configs API")
public class ConfigsApi {
#Autowired
private ConfigService configService;
#ApiOperation(value = "", notes = "Gets `Config` objects. ", response = Config.class, responseContainer = "List")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful response", response = Config.class) })
#RequestMapping(value = "", produces = { "application/json" }, method = RequestMethod.GET)
public ResponseEntity<List<Config>> getAllConfigs() throws NotFoundException {
return new ResponseEntity<List<Config>>(configService.getAll(),HttpStatus.OK);
}
#ApiOperation(value = "", notes = "Creates/updates `Config` object. ", response = Void.class)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "successful operation", response = Void.class),
#ApiResponse(code = 400, message = "Invalid Config", response = Void.class) })
#RequestMapping(value = "", produces = { "application/json" }, method = RequestMethod.POST)
public ResponseEntity<Void> createConfigByKey( #ApiParam(value = "Updated user object" ,required=true ) #RequestBody Config body ) throws NotFoundException {
configService.merge(body);
return new ResponseEntity<Void>(HttpStatus.OK);
}
}
Service
package io.swagger.service;
import com.google.common.collect.Lists;
import io.swagger.model.Config;
import io.swagger.repository.ConfigRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
#Slf4j
#Service
public class ConfigService {
#Autowired
private ConfigRepository configRepository;
public List<Config> getAll() {return Lists.newArrayList(configRepository.findAll());}
public void merge(Config c){configRepository.save(c);}
}
Repository
package io.swagger.repository;
import io.swagger.model.Config;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.GraphRepository;
public interface ConfigRepository extends GraphRepository<Config> {
}
Neo4j Configuration
package io.swagger.configuration;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
#Configuration
#EnableNeo4jRepositories("io.swagger.*")
public class InventoryApiNeo4jConfiguration extends Neo4jConfiguration {
#Value("${neo4j.ogm.driver}")
private String driver;
#Value("${neo4j.ogm.URI}")
private String uri;
#Value("${neo4j.ogm.connection.pool.size}")
private int connectionPoolSize;
#Value("${neo4j.ogm.encryption.level}")
private String encryptionLevel;
#Bean
public org.neo4j.ogm.config.Configuration getConfiguration() {
org.neo4j.ogm.config.Configuration configuration = new org.neo4j.ogm.config.Configuration();
configuration.driverConfiguration()
.setDriverClassName(driver)
.setURI(uri)
.setConnectionPoolSize(connectionPoolSize)
.setEncryptionLevel(encryptionLevel);
return configuration;
}
#Override
#Bean
public SessionFactory getSessionFactory() {
return new SessionFactory(getConfiguration(), "io.swagger");
}
}
Pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.swagger</groupId>
<artifactId>swagger-springboot-server</artifactId>
<packaging>jar</packaging>
<name>swagger-springboot-server</name>
<version>1.0.0</version>
<properties>
<springfox-version>2.4.0</springfox-version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RC1</version>
</parent>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--SpringFox dependencies -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${springfox-version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${springfox-version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>
<!-- Neo4J -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-ogm-bolt-driver</artifactId>
<version>2.0.3</version>
</dependency>
</dependencies><repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
Updated Spring boot from 1.4.0.RC1 to 1.4.0.RELEASE and now it does not occur.
I trying to build a JSF WebApplication using Neo4j as No-SQL-Databasing. I want to access Neo4j via Hibernate.
My dependencies are
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-bom</artifactId>
<version>5.0.1.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.hibernate.ogm</groupId>
<artifactId>hibernate-ogm-neo4j</artifactId>
</dependency>
My entities look like the following
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
private String name;
private Double age;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "owner", cascade = CascadeType.PERSIST)
private Set<Car> cars = new HashSet<>();
...
I save them within in the following class
#Stateless
public class StorageManager {
#PersistenceContext(unitName = "neo4j")
private EntityManager em;
public void savePerson(Person p) {
em.persist(p);
}
public void saveCar(Car c){
em.persist(c);
}
public void save(String personName, Double personAge, String carName) {
Person person = new Person(personName, personAge);
Car car = new Car(carName);
car.setOwner(person);
person.getCars().add(car);
em.persist(person);
}
My Persistence Unit
<persistence-unit name="mongo-ogm" transaction-type="JTA">
<provider>org.hibernate.ogm.jpa.HibernateOgmPersistence</provider>
<class>entities.Person</class>
<class>entities.Car</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.ogm.datastore.provider" value="neo4j_embedded" />
<property name="hibernate.ogm.neo4j.database_path" value="C:\Users\phe\Documents\Neo4j\sample" />
</properties>
</persistence-unit>
When I deploy the WebApp on my Wildlfy 9 and want to save Data I get an Exception
org.hibernate.resource.transaction.backend.jta.internal.JtaPlatformInaccessibleException: Unable to access TransactionManager or UserTransaction to make physical transaction delegate
Researches in the internet ended with no result. There are only a few tutorials. I have tried it with and without transaction-type="JTA" .
Have you any ideas?
I found the solution. As I am deploying to Wildlfy I can add the following to my Persistence Unit.
<property name="hibernate.transaction.jta.platform" value="JBossAS" />
This works for me.
I am unable to successfully get the "p:autocomplete" widget to work...
Using the autocomplete widget, as shown here...
<p:autoComplete id="abc" dropdown="true" value="#{testBean.parmMap['inputval']}" completeMethod="#{testBean.testList}" var="items" itemLabel="#{items.label}" itemValue="#{items}" converter="#{itemConverter}" ></p:autoComplete>
I am receiving the following error message...
javax.el.PropertyNotFoundException: /index.xhtml #18,245 itemLabel="#{items.label}": Property 'label' not found on type java.lang.String
I have not been able to get past this error. Not certain where the problem lies
--I've included most of the relevant code shown below. Thank you for any guidance you can provide me!
Here is the entire facelets page - index.xhtml...
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:util="http://java.sun.com/jsf/composite/util"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:p="http://primefaces.org/ui">
<f:view contentType="text/html">
<h:head>
<title>testprimeac</title>
<meta charset="utf-8" />
</h:head>
<h:body>
<h:form id="form1">
<p:autoComplete id="abc" dropdown="true" value="#{testBean.parmMap['inputval']}" completeMethod="#{testBean.testList}" var="items" itemLabel="#{items.label}" itemValue="#{items}" converter="#{itemConverter}" ></p:autoComplete>
</h:form>
</h:body>
</f:view>
</html>`
Here is the "Item" class...
package aaa.bbb.ccc.war;
public class Item
{
private String label;
private String value;
public String getLabel()
{
return label;
}
public void setLabel(String label)
{
this.label = label;
}
public String getValue()
{
return value;
}
public void setValue(String value)
{
this.value = value;
}
}
Here is the ItemConverter class...
package aaa.bbb.ccc.war;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
public class ItemConverter implements Converter
{
#Override
public Object getAsObject(FacesContext facesContext, UIComponent component, String submittedValue)
{
if (submittedValue.trim().equals(""))
{
return null;
}
else
{
itemList = getItemList();
try
{
for (Item item : itemList)
{
if (item.getValue().equalsIgnoreCase(submittedValue))
{
return item;
}
}
}
catch (Exception e)
{
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Conversion Error", "Not a valid item object"));
}
}
return null;
}
#Override
public String getAsString(FacesContext facesContext, UIComponent component, Object value)
{
if (value == null || value.equals(""))
{
return "";
}
else
{
return String.valueOf(((Item) value).getValue());
}
}
private static List<Item> itemList;
private static List<Item> getItemList()
{
if (null == itemList)
{
refData = getRefData();
itemList = refData.getTestList();
}
return itemList;
}
private static RefData refData;
private static RefData getRefData()
{
refData = (RefData) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("refData");
if (null == refData)
{
refData = new RefData();
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("refData", refData);
}
return refData;
}
}
Here is the TestBean class...
package aaa.bbb.ccc.war;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.faces.context.FacesContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component("testBean")
#Scope("request")
public class TestBean implements Serializable
{
private RefData refData;
private LinkedHashMap<String, String> parmMap;
public TestBean()
{
}
public Map<String, String> getParmMap()
{
refData = getRefData();
return refData.getParmMap();
}
public void setParmMap(LinkedHashMap<String, String> m)
{
refData = getRefData();
refData.setParmMap(m);
storeRefData(refData);
}
public void setTestList(List<Item> list) throws Exception
{
try
{
refData = getRefData();
refData.setTestList(list);
storeRefData(refData);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public List<Item> getTestList()
{
refData = getRefData();
return refData.getTestList();
}
private static RefData getRefData()
{
RefData refData = (RefData) FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("refData");
if (null == refData)
{
refData = new RefData();
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("refData", refData);
}
return refData;
}
private static void storeRefData(RefData r)
{
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("refData", r);
}
}
Here is the RefData class (referred to in TestBean)...
package aaa.bbb.ccc.war;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
public class RefData implements Serializable
{
public RefData() //(String key)
{
}
private static final Map<String, String> listBoxEntryMap;
static
{
Map<String, String> m = new LinkedHashMap<String, String>();
m.put("aaavalue", "aaalabel");
m.put("bbbvalue", "aablabel");
m.put("cccvalue", "abblabel");
m.put("dddvalue", "bbblabel");
m.put("eeevalue", "bbclabel");
m.put("fffvalue", "bcclabel");
m.put("gggvalue", "ccclabel");
m.put("hhhvalue", "ccalabel");
m.put("iiivalue", "caalabel");
m.put("jjjvalue", "aaclabel");
m.put("kkkvalue", "acclabel");
m.put("lllvalue", "bbalabel");
m.put("mmmvalue", "baalabel");
listBoxEntryMap = Collections.unmodifiableMap(m);
}
private Map<String, String> parmMap;
public Map getParmMap()
{
if (null == this.parmMap)
{
this.parmMap = new LinkedHashMap<String, String>();
this.parmMap.put("inputval", "");
}
return this.parmMap;
}
public void setParmMap(Map m)
{
this.parmMap = m;
}
List<Item> testList = new ArrayList<Item>();
public void setTestList(List<Item> data) throws IOException
{
testList = data;
}
public List<Item> getTestList()
{
try
{
if (null == testList || testList.isEmpty())
{
testList = getListOfItems();
}
return testList; //(list);
}
catch (Exception ex)
{
java.util.logging.Logger.getLogger(RefData.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public static List<Item> getListOfItems()
{
List<Item> list = null;
try
{
Map<String, String> map = listBoxEntryMap;
Iterator iter = map.keySet().iterator();
list = new ArrayList<Item>();
Item item = null;
while (iter.hasNext())
{
String key = (String) iter.next();
String val = (String) map.get(key);
item = new Item();
item.setLabel(key);
item.setValue(val);
list.add(item);
}
}
catch (Exception e)
{
System.out.println("Exception during query call..." + e.getMessage());
e.printStackTrace();
}
return list;
}
}
FWIW - Here is the pom.xml (which includes the primefaces dependency)...
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>aaa.bbb.ccc</groupId>
<artifactId>testprimeac-war</artifactId>
<packaging>war</packaging>
<version>1</version>
<name>testprimeac-war</name>
<url>http://maven.apache.org</url>
<properties>
<org.springframework-version>3.1.1.RELEASE</org.springframework-version>
<jsf-version>2.1.11</jsf-version>
</properties>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>el-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>el-impl</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
</plugin>
</plugins>
<finalName>testprimeac-${project.version}</finalName>
</build>
</project>
+1 for the assertions you made in your first paragraph: You do have too much information on here and the converter is unnecessary since you're binding to a basic String type.
The two main issues I see here Are
your choice to not use a type-safe collection in your value binding. Using a plain LinkedHashMap to hold String values will most likely cause problems as they will be stored as objects, I don't think the compiler is obliged to do any autoboxing for you here.
Your backing completeMethod implementation is essentially returninglkp a store of String objects. This is what provides the variable for var in the autocomplete . To state the obvious, you can't call getLabel() on that.
You can proceed either of 2 ways
Change your data store to a typesafe LinkedList of Strings , Lose the converter and you should be fine. Your var will be plain item and itemLabel and itemValue will both be #{item}.
Implement a POJO to encapsulate the selection object, keep the converter and then your backing data store becomes a simple list of your POJO and also your selection becomes an instance of the POJO instead of a String
EDIT: With your clarifications, the problem is as a result of the discrepancy in the type returned by the autocomplete dropdown (Type item) and the value binding of the autocomplete to the backing bean (String type). The type returned by the dropdown selection must be the same as the type you're binding to in the backing bean.