Rollback issue with Neo4j - neo4j

I am following this tutorial but have modified the POM.xml to use version 3.1.0.RELEASE of Spring Data Neo4j. I am experiencing a problem where the graph database does not seem to be instantiated correctly. The only code modification I did from the tutorial code was to replace the deprecated EmbeddedGraphDatabase API with the GraphDatabaseService API in Application.java. Any help would be appreciated.
UPDATE
This issue with the EmbdeedGraphDatabase is fixed. Now the issue is with rollback a transaction.
Application.java
package com.me.nosql.neo4j.hello;
import java.io.File;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.kernel.impl.util.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.neo4j.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.Transactional;
#Configuration
#EnableNeo4jRepositories
#EnableAutoConfiguration
#ComponentScan
public class Application extends Neo4jConfiguration implements CommandLineRunner {
#Autowired
PersonRepository personRepository;
public Application() {
setBasePackage("com/me/nosql/neo4j/hello");
}
#Bean(destroyMethod = "shutdown")
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory().newEmbeddedDatabase("accessingdataneo4j.db");
}
public void run(String... args) throws Exception {
Person greg = new Person("Greg");
Person roy = new Person("Roy");
Person craig = new Person("Craig");
System.out.println("Before linking up with Neo4j...");
for (Person person : new Person[]{greg, roy, craig}) {
System.out.println(person);
}
try (Transaction tx = graphDatabaseService().beginTx()) {
personRepository.save(greg);
personRepository.save(roy);
personRepository.save(craig);
greg = personRepository.findByName(greg.name);
greg.worksWith(roy);
greg.worksWith(craig);
personRepository.save(greg);
roy = personRepository.findByName(roy.name);
roy.worksWith(craig);
// We already know that roy works with greg
personRepository.save(roy);
// We already know craig works with roy and greg
tx.success();
}
System.out.println("Lookup each person by name...");
for (String name : new String[]{greg.name, roy.name, craig.name}) {
System.out.println(personRepository.findByName(name));
}
System.out.println("Looking up who works with Greg...");
for (Person person : personRepository.findByTeammatesName("Greg")) {
System.out.println(person.name + " works with Greg.");
}
}
public static void main(String[] args) throws Exception {
FileUtils.deleteRecursively(new File("accessingdataneo4j.db"));
SpringApplication.run(Application.class, args);
}
}
Error Message
Exception in thread "main" java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:637)
at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:652)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:909)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:898)
at com.me.nosql.neo4j.hello.Application.main(Application.java:84)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: org.neo4j.graphdb.TransactionFailureException: Failed to mark transaction as rollback only.
at org.neo4j.kernel.TopLevelTransaction.markAsRollbackOnly(TopLevelTransaction.java:97)
at org.neo4j.kernel.TopLevelTransaction.failure(TopLevelTransaction.java:86)
at org.neo4j.cypher.internal.spi.v2_0.TransactionBoundQueryContext.close(TransactionBoundQueryContext.scala:59)
at org.neo4j.cypher.internal.compiler.v2_0.spi.DelegatingQueryContext.close(DelegatingQueryContext.scala:33)
at org.neo4j.cypher.internal.compiler.v2_0.spi.ExceptionTranslatingQueryContext.org$neo4j$cypher$internal$compiler$v2_0$spi$ExceptionTranslatingQueryContext$$super$close(ExceptionTranslatingQueryContext.scala:34)
at org.neo4j.cypher.internal.compiler.v2_0.spi.ExceptionTranslatingQueryContext$$anonfun$close$1.apply$mcV$sp(ExceptionTranslatingQueryContext.scala:34)
at org.neo4j.cypher.internal.compiler.v2_0.spi.ExceptionTranslatingQueryContext$$anonfun$close$1.apply(ExceptionTranslatingQueryContext.scala:34)
at org.neo4j.cypher.internal.compiler.v2_0.spi.ExceptionTranslatingQueryContext$$anonfun$close$1.apply(ExceptionTranslatingQueryContext.scala:34)
at org.neo4j.cypher.internal.compiler.v2_0.spi.ExceptionTranslatingQueryContext.org$neo4j$cypher$internal$compiler$v2_0$spi$ExceptionTranslatingQueryContext$$translateException(ExceptionTranslatingQueryContext.scala:149)
at org.neo4j.cypher.internal.compiler.v2_0.spi.ExceptionTranslatingQueryContext.close(ExceptionTranslatingQueryContext.scala:34)
at org.neo4j.cypher.internal.compiler.v2_0.ClosingIterator$$anonfun$close$1.apply$mcV$sp(ClosingIterator.scala:65)
at org.neo4j.cypher.internal.compiler.v2_0.ClosingIterator$$anonfun$close$1.apply(ClosingIterator.scala:63)
at org.neo4j.cypher.internal.compiler.v2_0.ClosingIterator$$anonfun$close$1.apply(ClosingIterator.scala:63)
at org.neo4j.cypher.internal.compiler.v2_0.ClosingIterator.translateException(ClosingIterator.scala:70)
at org.neo4j.cypher.internal.compiler.v2_0.ClosingIterator.close(ClosingIterator.scala:62)
at org.neo4j.cypher.internal.compiler.v2_0.ClosingIterator.failIfThrows(ClosingIterator.scala:92)
at org.neo4j.cypher.internal.compiler.v2_0.ClosingIterator.hasNext(ClosingIterator.scala:34)
at org.neo4j.cypher.internal.compiler.v2_0.PipeExecutionResult.hasNext(PipeExecutionResult.scala:166)
at scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:327)
at scala.collection.convert.Wrappers$IteratorWrapper.hasNext(Wrappers.scala:29)
at org.neo4j.cypher.internal.compiler.v2_0.PipeExecutionResult$$anon$1.hasNext(PipeExecutionResult.scala:74)
at org.neo4j.helpers.collection.IteratorWrapper.hasNext(IteratorWrapper.java:42)
at com.me.nosql.neo4j.hello.Application.run(Application.java:77)
at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:634)
... 10 more
Caused by: java.lang.NullPointerException
at org.neo4j.kernel.TopLevelTransaction.markAsRollbackOnly(TopLevelTransaction.java:93)
... 33 more

I faced the similar problem and found the hint for the solution here (http://spring.io/guides/gs/accessing-neo4j-data-rest/).
I needed to add the constructor like this:
public Application() {
setBasePackage("name/of/the/base/package");
}
The example ran, created the db and performed queries, all except the last ("Looking up who works with Greg..."). The error was:
Caused by: org.neo4j.graphdb.TransactionFailureException: Failed to mark transaction as rollback only.
I managed to resolve this by wrapping the call in the transaction, just like that:
Transaction tx = graphDatabase.beginTx();
try {
//Do all the work
tx.success();
} finally {
tx.close();
}
After then, everything worked fine for me.
EDIT: In order to make all transactions persist in DB I changed the following lines to
try (Transaction tx = graphDatabase().beginTx()) {
System.out.println("Lookup each person by name...");
for (String name : new String[]{greg.name, roy.name, craig.name}) {
System.out.println(personRepository.findByName(name));
}
tx.success();
}
try (Transaction tx = graphDatabase().beginTx()) {
System.out.println("Looking up who works with Greg...");
for (PersonJ person : personRepository.findByTeammatesName("Greg")) {
System.out.println(person.name + " works with Greg.");
}
tx.success();
}

Few things you would like to check :
new GraphDatabaseFactory().newEmbeddedDatabase( "accessingdataneo4j.db" ); Check if the path to the db is correct . Try with an absolute address.
Check if you aren't running the GraphDb instance somewhere else.

Remove this:
#Autowired
GraphDatabase graphDatabase;
And use the bean-method instead:
graphDatabaseService()
// or
graphDatabase()
Try to use CommandlineRunner as a bean and inject the dependency there. Spring doesn't inject beans that are declared in this configuration into the same config instance.

Related

Grails 4 service not being injected inside Grails Data Service

This is about a Grails service injected into a Data Service. The problem is that the injected service is null at runtime. Here is an example.
class MessagingService {
def sendEmail(String message) {
...
}
}
interface IFlowService {
...
}
#Service(Flow)
abstract class FlowService implements IFlowService {
MessagingService messagingService
void sendFoo() {
messagingService.sendEmail(message)
}
}
FlowService and MessagingService both reside under grails-app/services.
When FlowService calls sendEmail there is an NPE because messagingService is null.
MessagingService is hand-written and is not associated with a domain.
This project uses Grails 4.0.10 and the issue occurred several times. When the usual Gails magic (i.e. injection) didn't work I solved the first one or two issues with kludges, you know, just to avoid getting stuck.
Now it seems to me the issue is quite predictable, it happens every time I write a service not associated with a domain. Did I miss something in the documentation? What is the appropriate way to handle this?
Kludge: To get around the issue I include a method sayHi in the problematic service. It just logs a debug message. I invoke sayHi from BootStrap to check that it works. It does, surprisingly. Then I add code in BootStrap to assign the service to the supposedly injected property in the service. [Shudder]
I tried to reproduce the same-
interface IFlowService {
}
#Service(Flow)
abstract class FlowService implements IFlowService {
MessagingService messagingService
void hello() {
println "hello"
messagingService.hi() // <- NPE
}
}
class MessagingService {
void hi() {
println "hi"
}
}
This seems to be a bug to be in Grails. But you can easily solve this (probably as a workaround) by just adding #Autowired in the service-
import org.springframework.beans.factory.annotation.Autowired
#Service(Flow)
abstract class FlowService implements IFlowService {
#Autowired
MessagingService messagingService
void hello() {
println "hello"
messagingService.hi() // <- No NPE
}
}
It prints-

How do I access datasources in ordinary groovy classes in Grails 3?

I'm trying to run sql-code from inside an ordinary Groovy class (no service).
In Grails 2 I could access a datasource by doing this:
public GroovyClass() {
def ctx = ServletContextHolder.servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
def dataSource = ctx.getBean('dataSource')
sql = new Sql(dataSource)
}
After migrating to Grails 3.3.8 the code no longer works. What is the correct method in Grails 3.3.8?
The "Hollywood Principle" says "Don't call us, we will call you" and that is what dependency injection is about. In your case, don't go get the dataSource, have the dataSource given to you.
There are a number of ways to do that. See the project at https://github.com/jeffbrown/asdudemo.
https://github.com/jeffbrown/asdudemo/blob/master/src/main/groovy/ascudemo/helpers/FirstGroovyClass.groovy
// src/main/groovy/ascudemo/FirstGroovyClass.groovy
package ascudemo.helpers
import groovy.sql.Sql
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.InitializingBean
import javax.sql.DataSource
#Slf4j
class FirstGroovyClass implements InitializingBean {
DataSource dataSource
Sql sql
void logSomeInfo() {
// both have been initialized
log.debug "dataSource: ${dataSource}"
log.debug "sql: ${sql}"
}
#Override
void afterPropertiesSet() throws Exception {
sql = new Sql(dataSource)
}
}
https://github.com/jeffbrown/asdudemo/blob/master/src/main/groovy/ascudemo/helpers/SecondGroovyClass.groovy
// src/main/groovy/ascudemo/SecondGroovyClass.groovy
package ascudemo.helpers
import groovy.sql.Sql
import groovy.util.logging.Slf4j
import javax.annotation.PostConstruct
import javax.sql.DataSource
#Slf4j
class SecondGroovyClass {
DataSource dataSource
Sql sql
void logSomeInfo() {
// both have been initialized
log.debug "dataSource: ${dataSource}"
log.debug "sql: ${sql}"
}
#PostConstruct
void initSql() throws Exception {
sql = new Sql(dataSource)
}
}
https://github.com/jeffbrown/asdudemo/blob/master/src/main/groovy/ascudemo/helpers/ThirdGroovyClass.groovy
// src/main/groovy/ascudemo/SecondGroovyClass.groovy
package ascudemo.helpers
import groovy.sql.Sql
import groovy.util.logging.Slf4j
import org.springframework.beans.BeansException
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationContextAware
import javax.sql.DataSource
#Slf4j
class ThirdGroovyClass implements ApplicationContextAware {
Sql sql
void logSomeInfo() {
// sql been initialized
log.debug "sql: ${sql}"
}
#Override
void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
DataSource dataSource = applicationContext.getBean('dataSource', DataSource)
sql = new Sql(dataSource)
}
}
https://github.com/jeffbrown/asdudemo/blob/master/grails-app/controllers/ascudemo/DemoController.groovy
// grails-app/controllers/ascudemo/DemoController.groovy
package ascudemo
class DemoController {
SomeService someService
def index() {
someService.logSomeInfo()
render 'Success'
}
}
https://github.com/jeffbrown/asdudemo/blob/master/grails-app/services/ascudemo/SomeService.groovy
// grails-app/services/ascudemo/SomeService.groovy
package ascudemo
import ascudemo.helpers.FirstGroovyClass
import ascudemo.helpers.SecondGroovyClass
import ascudemo.helpers.ThirdGroovyClass
class SomeService {
FirstGroovyClass firstGroovyBean
SecondGroovyClass secondGroovyBean
ThirdGroovyClass thirdGroovyBean
def logSomeInfo() {
firstGroovyBean.logSomeInfo()
secondGroovyBean.logSomeInfo()
thirdGroovyBean.logSomeInfo()
}
}
https://github.com/jeffbrown/asdudemo/blob/master/grails-app/conf/spring/resources.groovy
// grails-app/conf/spring/resources.groovy
import ascudemo.helpers.FirstGroovyClass
import ascudemo.helpers.SecondGroovyClass
import ascudemo.helpers.ThirdGroovyClass
beans = {
// demonstrates one approach
firstGroovyBean(FirstGroovyClass) { bean ->
bean.autowire = 'byName'
}
// demonstrates another approach
secondGroovyBean(SecondGroovyClass) { bean ->
bean.autowire = 'byName'
}
// demonstrates a third approach
thirdGroovyBean ThirdGroovyClass
}
Run the app and send a request to http://localhost:8080/demo/ and that will verify that all 3 approaches worked.
There are other ways to do this but I hope that one of the above will be helpful to you.
Best of luck!
I just tested this and it seems to give the datasource
def ds = Holders.grailsApplication.mainContext.getBean('dataSource')
println "DataSource: ${ds}" ---> DataSource: org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy#5e91ade8
Didn't try any operations on it, but that looks right.

Neo4j Procedure throwing error

I'm just starting to learn how to write procedures. My simple proof of concept still isn't passing muster when Neo4j starts up. Here is the code:
import java.util.ArrayList;
import java.util.stream.Stream;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.logging.Log;
import org.neo4j.procedure.Context;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Procedure;
public class Procedures {
#Context
public GraphDatabaseService db;
#Context
public Log log;
#Procedure( name = "create_user", mode = Mode.WRITE )
public Stream<Create_user_response> create_user() {
ArrayList<Create_user_response> myList = new ArrayList<>();
Create_user_response res1 = new Create_user_response();
res1.out = 1;
myList.add(res1);
Stream<Create_user_response> myStream = myList.stream();
return myStream;
}
}
Here's my Create_user_response class:
public class Create_user_response {
public int out;
}
When Neo4j starts up it complains that my procedure needs to return a stream of records. I'm new to streams so I must be doing something wrong but just can't figure it out.
I appreciate any help. Thanks.
Turns out I had a bad dependency,
<dependency>
<groupId>org.neo4j.test</groupId>
<artifactId>neo4j-harness</artifactId>
<version>3.2.3</version>
<scope>test</scope>
</dependency>
wasn't working. I found it in a tutorial and its in Maven, but for some reason I must not have been doing something correctly with it.

TransactionManagerException: Transaction is not current for this thread

I am trying load some data from a CSV file into a clean db instance. I did this by creating an ETL process using Apache Camel it reads the input CSV split the file into multiple lines and process each line in parallel using one transaction per line.
class ImportRouteBuilder extends RouteBuilder {
private final Importer importer
private final String endpoint
public ImportRouteBuilder(Importer importer, String endpoint) {
this.endpoint = endpoint
this.importer = importer
}
#Override
void configure() throws Exception {
from(endpoint)
.unmarshal(buildCsvDataFormat())
.split(body())
.parallelProcessing()
.bean(importer)
}
private static CsvDataFormat buildCsvDataFormat() {
CsvDataFormat csv = new CsvDataFormat();
csv.skipHeaderRecord = true
csv
}
}
#Slf4j
#Service
class CountryCsvImporter implements Importer {
#Autowired
private CountryRepository countryRepository
#Autowired
private Session session
#Override
void process(List record) {
Transaction tx = session.beginTransaction();
try {
importCountry(record)
tx.commit()
}catch (Throwable t) {
tx.rollback()
}
tx.close()
}
I did this because did not wanted to use LOAD CSV cypher or Neo4j import tool while my model is still evolving since it is very handy to use OGM while prototyping. But now I found a wall at the middle of the process OGM generates this TransactionManagerException, it looks like it was some threading issue managing transactions.
at com.cartrawler.service.CountryImportSpecification.Should be able to import country csv(CountryImportSpecification.groovy:9)
Caused by: org.neo4j.ogm.exception.TransactionManagerException: Transaction is not current for this thread
at org.neo4j.ogm.session.transaction.DefaultTransactionManager.rollback(DefaultTransactionManager.java:78)
at org.neo4j.ogm.transaction.AbstractTransaction.rollback(AbstractTransaction.java:65)
at org.neo4j.ogm.drivers.embedded.transaction.EmbeddedTransaction.rollback(EmbeddedTransaction.java:60)
at com.cartrawler.service.CountryCsvImporter.process(CountryCsvImporter.groovy:28)
Thank you, kindly
Luis Oscar
Turn off parallel processing as transaction is single thread only.

ViewMapListener JSF not being called

I'm trying to port the JSF #ViewScoped annotation to CDI. The reason is more educational rather than based on need. I chose this particular scope mainly due to the lack of a better concrete example of a custom scope one might want to implement in CDI.
That said, my starting point was Porting the #ViewScoped JSF annotation to CDI. But, this implementation does not take into account a seemingly very important responsibility of Context (i.e. destroying) mentioned in the API:
The context object is responsible for creating and destroying contextual instances by calling operations of Contextual. In particular, the context object is responsible for destroying any contextual instance it creates by passing the instance to Contextual.destroy(Object, CreationalContext). A destroyed instance must not subsequently be returned by get(). The context object must pass the same instance of CreationalContext to Contextual.destroy() that it passed to Contextual.create() when it created the instance.
I decided to add this functionality by having my Context object:
keep track of what Contextual objects it creates for which UIViewRoots;
implement the ViewMapListener interface and register itself as listener for each UIViewRoot by calling UIViewRoot.subscribeToViewEvent(PreDestroyViewMapEvent.class, this);
destroy any created Contextuals when the ViewMapListener.processEvent(SystemEvent event) is called and unregister itself from that UIViewRoot.
Here's my Context implementation:
package com.example;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PreDestroyViewMapEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.ViewMapListener;
public class ViewContext implements Context, ViewMapListener {
private Map<UIViewRoot, Set<Disposable>> state;
public ViewContext() {
this.state = new HashMap<UIViewRoot, Set<Disposable>>();
}
// mimics a multimap put()
private void put(UIViewRoot key, Disposable value) {
if (this.state.containsKey(key)) {
this.state.get(key).add(value);
} else {
HashSet<Disposable> valueSet = new HashSet<Disposable>(1);
valueSet.add(value);
this.state.put(key, valueSet);
}
}
#Override
public Class<? extends Annotation> getScope() {
return ViewScoped.class;
}
#Override
public <T> T get(final Contextual<T> contextual,
final CreationalContext<T> creationalContext) {
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
FacesContext ctx = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = ctx.getViewRoot();
Map<String, Object> viewMap = viewRoot.getViewMap();
if (viewMap.containsKey(name)) {
return (T) viewMap.get(name);
} else {
final T instance = contextual.create(creationalContext);
viewMap.put(name, instance);
// register for events
viewRoot.subscribeToViewEvent(
PreDestroyViewMapEvent.class, this);
// allows us to properly couple the right contaxtual, instance, and creational context
this.put(viewRoot, new Disposable() {
#Override
public void dispose() {
contextual.destroy(instance, creationalContext);
}
});
return instance;
}
} else {
return null;
}
}
#Override
public <T> T get(Contextual<T> contextual) {
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
FacesContext ctx = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = ctx.getViewRoot();
Map<String, Object> viewMap = viewRoot.getViewMap();
if (viewMap.containsKey(name)) {
return (T) viewMap.get(name);
} else {
return null;
}
} else {
return null;
}
}
// this scope is only active when a FacesContext with a UIViewRoot exists
#Override
public boolean isActive() {
FacesContext ctx = FacesContext.getCurrentInstance();
if (ctx == null) {
return false;
} else {
UIViewRoot viewRoot = ctx.getViewRoot();
return viewRoot != null;
}
}
// dispose all of the beans associated with the UIViewRoot that fired this event
#Override
public void processEvent(SystemEvent event)
throws AbortProcessingException {
if (event instanceof PreDestroyViewMapEvent) {
UIViewRoot viewRoot = (UIViewRoot) event.getSource();
if (this.state.containsKey(viewRoot)) {
Set<Disposable> valueSet = this.state.remove(viewRoot);
for (Disposable disposable : valueSet) {
disposable.dispose();
}
viewRoot.unsubscribeFromViewEvent(
PreDestroyViewMapEvent.class, this);
}
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof UIViewRoot;
}
}
Here's the Disposable interface:
package com.example;
public interface Disposable {
public void dispose();
}
Here's the scope annotation:
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.enterprise.context.NormalScope;
#Inherited
#NormalScope
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD,
ElementType.FIELD, ElementType.PARAMETER})
public #interface ViewScoped {
}
Here's the CDI extension declaration:
package com.example;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
public class CustomContextsExtension implements Extension {
public void afterBeanDiscovery(#Observes AfterBeanDiscovery event) {
event.addContext(new ViewContext());
}
}
I added the javax.enterprise.inject.spi.Extension file under META-INF/services containing com.example.CustomContextsExtension to properly register the above with CDI.
I can now make beans like (notice the use of the custom #ViewScoped implementation.):
package com.example;
import com.concensus.athena.framework.cdi.extension.ViewScoped;
import java.io.Serializable;
import javax.inject.Named;
#Named
#ViewScoped
public class User implements Serializable {
...
}
The beans are created properly and properly injected into JSF pages (i.e. the same instance is returned per view, new ones are created only when the view is created, the same instances are injected over multiple requests to the same view). How do I know? Imagine the above code littered with debugging code which I purposefully stripped out for clarity and since this is already a huge post.
The problem is that my ViewContext.isListenerForSource(Object source) and ViewContext.processEvent(SystemEvent event) are never called. I was expecting that at least upon session expiration those events would be called, since the view map is stored in the session map (correct?). I set the session timeout to 1 minute, waited, saw the timeout happen, but my listener was still not called.
I also tried adding the following to my faces-config.xml (mostly out of the lack of ideas):
<system-event-listener>
<system-event-listener-class>com.example.ViewContext</system-event-listener-class>
<system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
<source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>
Finally, my environment is JBoss AS 7.1.1 with Mojarra 2.1.7.
Any clues would be greatly appreciated.
EDIT: Further Investigation.
PreDestroyViewMapEvent doesn't seem to be fired at all while PostConstructViewMapEvent is fired as expected - every time a new view map is created, specifically during UIViewRoot.getViewMap(true). The documentation states that PreDestroyViewMapEvent should be fired every time clear() is called on the view map. That leaves to wonder - is clear() required to be called at all? If so, when?
The only place in the documentation that I was able to find such a requirement is in FacesContext.setViewRoot():
If the current UIViewRoot is non-null, and calling equals() on the
argument root, passing the current UIViewRoot returns false, the clear
method must be called on the Map returned from UIViewRoot#getViewMap.
Does this ever happen in the normal JSF lifecycle, i.e. without programmatically calling UIViewRoot.setViewMap()? I can't seem to find any indication.
This is related to an issue with the JSF spec that is being fixed in the JSF2.2 spec, see here. Also, I created an issue with Apache DeltaSpike so they may try to fix it, see here. If it's fixed in DeltaSpike, then it may end up being merged into CODI and / or Seam as well.
The view map is stored in a LRU map, because you never know which view will be post back. Unfortunately, the PreDestroyViewMapEvent is not called before removing from this map.
A workaround is to reference your object from within a WeakReference. You can use ReferenceQueue or check the reference when to call your destruction code.

Resources