Lazy/Eager loading/fetching in Neo4j/Spring-Data - neo4j

I have a simple setup and encountered a puzzling (at least for me) problem:
I have three pojos which are related to each other:
#NodeEntity
public class Unit {
#GraphId Long nodeId;
#Indexed int type;
String description;
}
#NodeEntity
public class User {
#GraphId Long nodeId;
#RelatedTo(type="user", direction = Direction.INCOMING)
#Fetch private Iterable<Worker> worker;
#Fetch Unit currentUnit;
String name;
}
#NodeEntity
public class Worker {
#GraphId Long nodeId;
#Fetch User user;
#Fetch Unit unit;
String description;
}
So you have User-Worker-Unit with a "currentunit" which marks in user that allows to jump directly to the "current unit". Each User can have multiple workers, but one worker is only assigned to one unit (one unit can have multiple workers).
What I was wondering is how to control the #Fetch annotation on "User.worker". I actually want this to be laoded only when needed, because most of the time I only work with "Worker".
I went through http://static.springsource.org/spring-data/data-neo4j/docs/2.0.0.RELEASE/reference/html/ and it isn't really clear to me:
worker is iterable because it should be read only (incoming relation) - in the documentation this is stated clarly, but in the examples ''Set'' is used most of the time. Why? or doesn't it matter...
How do I get worker to only load on access? (lazy loading)
Why do I need to annotate even the simple relations (worker.unit) with #Fetch. Isn't there a better way? I have another entity with MANY such simple relations - I really want to avoid having to load the entire graph just because i want to the properties of one object.
Am I missing a spring configuration so it works with lazy loading?
Is there any way to load any relationships (which are not marked as #Fetch) via an extra call?
From how I see it, this construct loads the whole database as soon as I want a Worker, even if I don't care about the User most of the time.
The only workaround I found is to use repository and manually load the entities when needed.
------- Update -------
I have been working with neo4j quite some time now and found a solution for the above problem that does not require calling fetch all the time (and thus does not load the whole graph). Only downside: it is a runtime aspect:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.neo4j.annotation.NodeEntity;
import org.springframework.data.neo4j.support.Neo4jTemplate;
import my.modelUtils.BaseObject;
#Aspect
public class Neo4jFetchAspect {
// thew neo4j template - make sure to fill it
#Autowired private Neo4jTemplate template;
#Around("modelGetter()")
public Object autoFetch(ProceedingJoinPoint pjp) throws Throwable {
Object o = pjp.proceed();
if(o != null) {
if(o.getClass().isAnnotationPresent(NodeEntity.class)) {
if(o instanceof BaseObject<?>) {
BaseObject<?> bo = (BaseObject<?>)o;
if(bo.getId() != null && !bo.isFetched()) {
return template.fetch(o);
}
return o;
}
try {
return template.fetch(o);
} catch(MappingException me) {
me.printStackTrace();
}
}
}
return o;
}
#Pointcut("execution(public my.model.package.*.get*())")
public void modelGetter() {}
}
You just have to adapt the classpath on which the aspect should be applied: my.model.package..get())")
I apply the aspect to ALL get methods on my model classes. This requires a few prerequesites:
You MUST use getters in your model classes (the aspect does not work on public attributes - which you shouldn't use anyways)
all model classes are in the same package (so you need to adapt the code a little) - I guess you could adapt the filter
aspectj as a runtime component is required (a little tricky when you use tomcat) - but it works :)
ALL model classes must implement the BaseObject interface which provides:
public interface BaseObject {
public boolean isFetched();
}
This prevents double-fetching. I just check for a subclass or attribute that is mandatory (i.e. the name or something else except nodeId) to see if it is actually fetched. Neo4j will create an object but only fill the nodeId and leave everything else untouched (so everything else is NULL).
i.e.
#NodeEntity
public class User implements BaseObject{
#GraphId
private Long nodeId;
String username = null;
#Override
public boolean isFetched() {
return username != null;
}
}
If someone finds a way to do this without that weird workaround please add your solution :) because this one works, but I would love one without aspectj.
Base object design that doenst require a custom field check
One optimization would be to create a base-class instead of an interface that actually uses a Boolean field (Boolean loaded) and checks on that (so you dont need to worry about manual checking)
public abstract class BaseObject {
private Boolean loaded;
public boolean isFetched() {
return loaded != null;
}
/**
* getLoaded will always return true (is read when saving the object)
*/
public Boolean getLoaded() {
return true;
}
/**
* setLoaded is called when loading from neo4j
*/
public void setLoaded(Boolean val) {
this.loaded = val;
}
}
This works because when saving the object "true" is returned for loaded. When the aspect looks at the object it uses isFetched() which - when the object is not yet retrieved will return null. Once the object is retrieved setLoaded is called and the loaded variable set to true.
How to prevent jackson from triggering the lazy loading?
(As an answer to the question in the comment - note that I didnt try it out yet since I did not have this issue).
With jackson I suggest to use a custom serializer (see i.e. http://www.baeldung.com/jackson-custom-serialization ). This allows you to check the entity before getting the values. You simply do a check if it is already fetched and either go on with the whole serialization or just use the id:
public class ItemSerializer extends JsonSerializer<BaseObject> {
#Override
public void serialize(BaseObject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
// serialize the whole object
if(value.isFetched()) {
super.serialize(value, jgen, provider);
return;
}
// only serialize the id
jgen.writeStartObject();
jgen.writeNumberField("id", value.nodeId);
jgen.writeEndObject();
}
}
Spring Configuration
This is a sample Spring configuration I use - you need to adjust the packages to your project:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/neo4j http://www.springframework.org/schema/data/neo4j/spring-neo4j-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<context:annotation-config/>
<context:spring-configured/>
<neo4j:repositories base-package="my.dao"/> <!-- repositories = dao -->
<context:component-scan base-package="my.controller">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- that would be our services -->
</context:component-scan>
<tx:annotation-driven mode="aspectj" transaction-manager="neo4jTransactionManager"/>
<bean class="corinis.util.aspects.Neo4jFetchAspect" factory-method="aspectOf"/>
</beans>
AOP config
this is the /META-INF/aop.xml for this to work:
<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- only weave classes in our application-specific packages -->
<include within="my.model.*" />
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="my.util.aspects.Neo4jFetchAspect" />
</aspects>
</aspectj>

Found the answer to all the questions myself:
#Iterable: yes, iterable can be used for readonly
#load on access: per default nothing is loaded. and automatic lazy loading is not available (at least as far as I can gather)
For the rest:
When I need a relationship I either have to use #Fetch or use the neo4jtemplate.fetch method:
#NodeEntity
public class User {
#GraphId Long nodeId;
#RelatedTo(type="user", direction = Direction.INCOMING)
private Iterable<Worker> worker;
#Fetch Unit currentUnit;
String name;
}
class GetService {
#Autowired private Neo4jTemplate template;
public void doSomethingFunction() {
User u = ....;
// worker is not avaiable here
template.fetch(u.worker);
// do something with the worker
}
}

Not transparent, but still lazy fetching.
template.fetch(person.getDirectReports());
And #Fetch does the eager fetching as was already stated in your answer.

I like the aspect approach to work around the limitation of the current spring-data way to handle lazy loading.
#niko - I have put your code sample in a basic maven project and tried to get that solution to work with little success:
https://github.com/samuel-kerrien/neo4j-aspect-auto-fetching
For some reasons the Aspect is initialising but the advice doesn't seem to get executed. To reproduce the issue, just run the following JUnit test:
playground.neo4j.domain.UserTest

Related

How can I inject with Guice my api into dataflow jobs without needed to be serializable?

This question is a follow on after such a great answer Is there a way to upload jars for a dataflow job so we don't have to serialize everything?
This made me realize 'ok, what I want is injection with no serialization so that I can mock and test'.
Our current method requires our apis/mocks to be serialiable BUT THEN, I have to put static fields in the mock because it gets serialized and deserialized creating a new instance that dataflow uses.
My colleague pointed out that perhaps this needs to be a sink and that is treated differently? <- We may try that later and update but we are not sure right now.
My desire is from the top to replace the apis with mocks during testing. Does someone have an example for this?
Here is our bootstrap code that does not know if it is in production or inside a feature test. We test end to end results with no apache beam imports in our tests meaning we swap to any tech if we want to pivot and keep all our tests. Not only that, we catch way more integration bugs and can refactor without rewriting tests since the contracts we test are customer ones we can't easily change.
public class App {
private Pipeline pipeline;
private RosterFileTransform transform;
#Inject
public App(Pipeline pipeline, RosterFileTransform transform) {
this.pipeline = pipeline;
this.transform = transform;
}
public void start() {
pipeline.apply(transform);
pipeline.run();
}
}
Notice that everything we do is Guice Injection based so the Pipeline may be direct runner or not. I may need to modify this class to pass things through :( but anything that works for now would be great.
The function I am trying to get our api(and mock and impl to) with no serialization is thus
private class ValidRecordPublisher extends DoFn<Validated<PractitionerDataRecord>, String> {
#ProcessElement
public void processElement(#Element Validated<PractitionerDataRecord>element) {
microServiceApi.writeRecord(element.getValue);
}
}
I am not sure how to pass in microServiceApi in a way that avoid serialization. I would be ok with delayed creation as well after deserialization using guice Provider provider; with provider.get() if there is a solution there too.
Solved in such a way that mocks no longer need static or serialization anymore by one since glass bridging the world of dataflow(in prod and in test) like so
NOTE: There is additional magic-ness we have in our company that passes through headers from service to service and through dataflow and that is some of it in there which you can ignore(ie. the RouterRequest request = Current.request();). so for anyone else, they will have to pass in projectId into getInstance each time.
public abstract class DataflowClientFactory implements Serializable {
private static final Logger log = LoggerFactory.getLogger(DataflowClientFactory.class);
public static final String PROJECT_KEY = "projectKey";
private transient static Injector injector;
private transient static Module overrides;
private static int counter = 0;
public DataflowClientFactory() {
counter++;
log.info("creating again(usually due to deserialization). counter="+counter);
}
public static void injectOverrides(Module dfOverrides) {
overrides = dfOverrides;
}
private synchronized void initialize(String project) {
if(injector != null)
return;
/********************************************
* The hardest part is this piece since this is specific to each Dataflow
* so each project subclasses DataflowClientFactory
* This solution is the best ONLY in the fact of time crunch and it works
* decently for end to end testing without developers needing fancy
* wrappers around mocks anymore.
***/
Module module = loadProjectModule();
Module modules = Modules.combine(module, new OrderlyDataflowModule(project));
if(overrides != null) {
modules = Modules.override(modules).with(overrides);
}
injector = Guice.createInjector(modules);
}
protected abstract Module loadProjectModule();
public <T> T getInstance(Class<T> clazz) {
if(!Current.isContextSet()) {
throw new IllegalStateException("Someone on the stack is extending DoFn instead of OrderlyDoFn so you need to fix that first");
}
RouterRequest request = Current.request();
String project = (String)request.requestState.get(PROJECT_KEY);
initialize(project);
return injector.getInstance(clazz);
}
}
I suppose this may not be what you're looking for, but your use case makes me think of using factory objects. They may depend on the pipeline options that you pass (i.e. your PipelineOptions object), or on some other configuration object.
Perhaps something like this:
class MicroserviceApiClientFactory implements Serializable {
MicroserviceApiClientFactory(PipelineOptions options) {
this.options = options;
}
public static MicroserviceApiClient getClient() {
MyPipelineOptions specialOpts = options.as(MySpecialOptions.class);
if (specialOpts.getMockMicroserviceApi()) {
return new MockedMicroserviceApiClient(...); // Or whatever
} else {
return new MicroserviceApiClient(specialOpts.getMicroserviceEndpoint()); // Or whatever parameters it needs
}
}
}
And for your DoFns and any other execution-time objects that need it, you would pass the factory:
private class ValidRecordPublisher extends DoFn<Validated<PractitionerDataRecord>, String> {
ValidRecordPublisher(MicroserviceApiClientFactory msFactory) {
this.msFactory = msFactory;
}
#ProcessElement
public void processElement(#Element Validated<PractitionerDataRecord>element) {
if (microServiceapi == null) microServiceApi = msFactory.getClient();
microServiceApi.writeRecord(element.getValue);
}
}
This should allow you to encapsulate the mocking functionality into a single class that lazily creates your mock or your client at pipeline execution time.
Let me know if this matches what you want somewhat, or if we should try to iterate further.
I have no experience with Guice, so I don't know if Guice configurations can easily pass the boundary between pipeline construction and pipeline execution (serialization / submittin JARs / etc).
Should this be a sink? Maybe, if you have an external service, and you're writing to it, you can write a PTransform that takes care of it - but the question of how you inject various dependencies will remain.

Veto a CDI bean if another of the same type is present

Not sure if I'm thinking right about this, I'm looking[in CDI] for something similar to what we have in Spring - #ConditionalOnMissingBean - that allows you tell spring - create only if the bean specified is missing.
I've tried using extensions, looks like one can tap several events, and use those to VETO beans. One way might be to have BeanManager at this stage, and look for already present beans, and if it contains the one you're about to inject, VETO this one. BUT, this would only work when we HAVE LOOKED AT ALL the beans.
AfterBeanDiscovery looks suitable, however, before it is invoked, validation fails, complaining of multiple beans of the same type.
Would be great if I could get some help here.
Your question is interesting and can be solved using a CDI extension (almost as you describe, actually), see below for a naive, working, proof-of-concept implementation. It is naive because it does not handle e.g. producer methods/fields and may be missing more.
CDI extensions are really great and powerful, but can be rather technical, so let's discuss other alternatives first.
Specialization: Maybe it is enough for your use case to document explicitly that you provide the default implementation of SomeService through, say, public class SomeServiceDefaultImpl and in order to override it the developer should do:
#Specializes
public class SomeServiceSpecialImpl extends SomeServiceDefaultImpl {...}
Also consider the alternatives, as mentioned in the comment from John Ament.
Qualifiers: If this service is used only in one place/a few places and only inside your code, you could qualify your SomeServiceDefaultImpl with a custom qualifier, say #MyDefaultImpl. Then inject an Instance<SomeService>, look for an unqualified instance first and, if that is not satisfied, look for the qualified - something along the lines of:
private SomeService someService;
#Inject
void setSomeServiceInstance(Instance<SomeService> s) {
// not tried, please adapt as needed
if( s.isUnsatisfied() ) {
someService = s.select(new MyDefaultImplAnnotation()).get();
}
else {
someService = s.get();
}
}
Provide a default implementation that is #Vetoed so as to force the client of your code to provide an implementation. If the client wants to use the default, they can simply use a producer.
Having said the above, the implementation below is a proof of concept that:
Requires the following annotation to be present on the default implementation:
#Target({ TYPE, METHOD, FIELD })
#Retention(RUNTIME)
#Documented
public #interface ConditionalOnMissingBean {
Class<?> value();
}
The value() is required and denotes the bean type that is "defaulted". Your implementation can be smarter, i.e. detect the bean type from the actual default implementation, but, hey, that's only a proof of concept!
Blatantly ignores producers!
Is lightly tested, so there are probably evil corner cases, so BEWARE!
In addition to the code you need all the choreography of an extension (META-INF/services/javax.enterprise.inject.spi.Extension, beans.xml).
import java.util.HashMap;
import java.util.Map;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanAttributes;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionTargetFactory;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
public class ConditionalOnMissingBeanExtension implements Extension {
private Map<Class<?>, AnnotatedType<?>> map = new HashMap<>();
<T> void processAnnotatedType(#Observes ProcessAnnotatedType<T> pat) {
AnnotatedType<?> annotatedType = pat.getAnnotatedType();
ConditionalOnMissingBean annotation = annotatedType.getAnnotation(ConditionalOnMissingBean.class);
if( annotation != null ) {
map.put(annotation.value(), annotatedType);
pat.veto();
}
}
void afterBeanDiscovery(#Observes AfterBeanDiscovery abd, BeanManager beanManager) {
map.entrySet().stream()
.filter(e -> doesNotHaveBeanOfType(beanManager, e.getKey()))
.map(e -> defineBean(beanManager, e.getValue()))
.forEach(abd::addBean);
map = null;
}
private boolean doesNotHaveBeanOfType(BeanManager beanManager, Class<?> type) {
return beanManager.getBeans(type).isEmpty();
}
private <T> Bean<T> defineBean(BeanManager beanManager, AnnotatedType<T> annotatedType) {
BeanAttributes<T> beanAttributes = beanManager.createBeanAttributes(annotatedType);
InjectionTargetFactory<T> injectionTargetFactory = beanManager.getInjectionTargetFactory(annotatedType);
return beanManager.createBean(beanAttributes, annotatedType.getJavaClass(), injectionTargetFactory);
}
}
An example of a default implementation of a service interface would be:
#ApplicationScoped
#ConditionalOnMissingBean(SomeService.class)
public class SomeServiceDefaultImpl implements SomeService {
#Override
public String doSomeCalculation() {
return "from default implementation";
}
}

CDI, polymorphism: Is it possible to inject an implementation of bean B into a bean A based on a field initialized during #PostConstruct of A?

In my JSF application I'm using a #ViewScoped bean Publication to show/edit data coming from my database. In that bean there is a field for a subtype-specific data object, i.e. containing a different object depending on whether the publication is, say, a book or an article.
#ViewScoped
#Named
public class Publication implements Serializable {
#Inject
DatabaseStorage storage;
...
String id;
String type;
PublicationType typedStuff;
#PostConstruct
public void init() {
// Get an URL parameter from the request,
// look up row in database accordingly, initialize String "type".
switch (type) {
case "ARTICLE":
typedStuff = new Article(id);
break;
case "BOOK":
typedStuff = new Book(id);
break;
default:
break;
}
}
}
...with classes Article and Book that implement / extend PublicationType.
So far, so good, but I would like for typedStuff to be a CDI bean, so that I can inject useful resources there.
I've read this and this page on producer methods, as well as this tutorial and this very related SO question, but none of them answer precisely my question: Can I inject based on a field that the injecting bean itself only knows at runtime?
I've gotten the producer method to work as such, but I can't parametrize it, so I can't get that switch to work.
If I put the producer method in a separate class (or bean) then I don't have access to the type field.
If I inject the injecting bean into the producer class, or move the producer method into the injecting class, I get a circular injection.
If I put the producer method statically into the injecting class, I also don't have access, because type cannot be static. (Although, since it's only used momentarily...?)
Also (and that is probably the answer right there), the producer method is executed before my injecting bean's init method, so type wouldn't even have been set yet.
Does anybody have a better idea?
No you cannot, but you can select a bean based on the field value. Say:
public interface PublicationType {}
#PType("ARTICLE")
public class Article implements PublicationType{}
#PType("BOOK")
public class Book implements PublicationType {}
And define a qualifier:
public #interface PType {
String value();
}
And define an AnnotationLiteral:
public class PTypeLiteral extends AnnotationLiteral<PType> implements PType {}
Then you can use:
public class Publication {
#Any
#Inject
private Instance<PublicationType> publicationTypes;
public void doSomething() {
PType ptype = new PTypeLiteral(type);
// Of course you will have to handle all the kind of exceptions here.
PublicationType publicationType = publicationTypes.select(ptype).get();
}
}
There is the javax.inject.Provider interface (I think You are using #Named and #Inject annotations from the same package).
You could use it to achieve what You want. It will create instances for You with injected fields.
One drawback is that You will have to set the id yourself.
#ViewScoped
#Named
public class Publication implements Serializable {
#Inject
DatabaseStorage storage;
#Inject
Provider<Article> articleProvider;
#Inject
Provider<Book> bookProvider;
String id;
String type;
PublicationType typedStuff;
#PostConstruct
public void init() {
// Get an URL parameter from the request,
// look up row in database accordingly, initialize String "type".
switch (type) {
case "ARTICLE":
typedStuff = articleProvider.get();
typedStuff.setId(id);
break;
case "BOOK":
typedStuff = bookProvider.get();
typedStuff.setId(id);
break;
default:
break;
}
}
}

#PreAuthorize: reference property in implementing class

I have service interface
public interface CompoundService<T extends Compound> {
T getById(final Long id);
//...
}
and abstract implementation
public abstract class CompoundServiceImpl<T extends Compound>
implements CompoundService<T> {
//...
private Class<T> compoundClass;
//...
}
Every implementation of Compound requires it's own service interface which extends CompoundService and it's own service class which extends CompoundServiceImpl.
I would now like to add basic security uisng annotations to my methods in CompoundService. As far as I understood I must add them in the interface not the actual implementation. Since a user can have different roles for different implementations of Compound, i must take this into account. Meaning in #PreAuthorize I would like to get the name of the Compound implementation, eg. compoundClass.getSimpleName(). So that I get something like:
public interface CompoundService<T extends Compound> {
#PreAuthorize("hasRole('read_' + #root.this.compoundClass.getSimpleName())")
T getById(final Long id);
//...
}
This is basically what is mentioned here:
https://jira.springsource.org/browse/SEC-1640
however there is no example and I did not really get the solution. So should i use this? or as above #root.this?
My second question is, since this is in an interface which will be implemented by a proxy (from spring) will the experession this.compoundClass actually evaluate properly?
And last but not least how can I actually test this?*
*
I'm not actually creating a finished application but something configurable, like a framework for s specific type of database search. Meaning most authorization and authentication stuff has to come from the implementer.
Unit Testing
see http://www.lancegleason.com/blog/2009/12/07/unit-testing-spring-security-with-annotations
Since that is an old tutorial you might need to change the referenced schema versions. But more importantly the SecurityContext.xml configuration shown there does not work with Spring Security 3. See Spring Security - multiple authentication-providers for a proper configuration.
I did not require the mentioned dependencies:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core-tiger</artifactId>
</dependency>
it worked without them (however did not create an abstract test class)
root.this
This is in fact correct approach
The problem is that you can't use getSimpleName() of a class parameter. For an in-depth discussion see http://forum.springsource.org/showthread.php?98570-Getting-Payload-Classname-in-Header-Enricher-via-SpEL
The workarounds shown there did not help me much. So I came up with this very simple solution:
Just add the string property String compoundClassSimpleName to CompoundServiceImpl and set it in the constructor (which is called by subclasses):
Public abstract class CompoundServiceImpl<T extends Compound>
implements CompoundService<T> {
private String compoundClassSimpleName;
//...
public ChemicalCompoundServiceImpl(Class<T> compoundClass) {
this.compoundClass = compoundClass;
this.compoundClassSimpleName = compoundClass.getSimpleName();
}
//...
public String getCompoundClassSimpleName(){
return compoundClassSimpleName;
}
}
and her a Service implementing above abstract service:
public class TestCompoundServiceImpl extends CompoundServiceImpl<TestCompound>
implements TestCompoundService {
//...
public TestCompoundServiceImpl() {
super(TestCompound.class);
}
//...
}
And final the #PreAuthorize annotation usage:
public interface CompoundService<T extends Compound> {
#PreAuthorize("hasRole('read_' + #root.this.getCompoundClassSimpleName())")
public T getById(final Long id);
}
For above example the expression will evaluate to a role named "read_TestCompound".
Done!
As often the solution is very simple but getting there is a PITA...
EDIT:
for completeness the test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {
"classpath:ApplicationContext.xml",
"classpath:SecurityContext.xml"
})
public class CompoundServiceSecurityTest {
#Autowired
#Qualifier("testCompoundService")
private TestCompoundService testCompoundService;
public CompoundServiceSecurityTest() {
}
#Before
public void setUp() {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("user_test", "pass1"));
}
#Test
public void testGetById() {
System.out.println("getById");
Long id = 1000L;
TestCompound expResult = new TestCompound(id, "Test Compound");
TestCompound result = testCompoundService.getById(id);
assertEquals(expResult, result);
}
}

Instance method with Guice

I would like to have a static instance method with Guice for one of the components (non-managed bean should be able to access this class). I created something like this:
public class LookupService {
#Inject
private static Provider<Injector> injector = null;
private final ILookup<IWS> lookup;
#Inject
public LookupService(ILookup<IWS> lookup) {
this.lookup = lookup;
}
public static LookupService instance() {
return injector.get().getInstance(LookupService.class);
}
public <T extends IWS> T lookup(Class<T> localInterface) {
return lookup.lookup(localInterface);
}
}
What do you think about this design ? Any other ideas on this ? (accessing managed beans from non-managed objects)
Basically, the pattern you're looking for is called "requesting static injection" and there's a Binder method dedicated to it. Once you have that down, your code looks a lot like this example from the Guice docs.
public class MainModule extends AbstractModule {
#Override public void configure() {
requestStaticInjection(LookupService.class);
}
}
public class LookupService {
/** This will be set as soon as the injector is created. */
#Inject
static Provider<LookupService> provider = null;
private final ILookup<IWS> lookup;
#Inject
public LookupService(ILookup<IWS> lookup) {
this.lookup = lookup;
}
public static LookupService instance() {
return provider.get();
}
public <T extends IWS> T lookup(Class<T> localInterface) {
return lookup.lookup(localInterface);
}
}
A few notes:
While you can still set your field to be private, remember that this means you cannot set it in tests (or in future non-Guice usage) without Guice's private-field-access magic. When using injected fields, we often make them package-private and then put the tests in the same package.
Static injection is generally seen as something to endorse only when migrating to Guice, or when you use other code you can't change. When possible, try to avoid global state--even if this means making FooBean data-only and creating an injected FooBeanService.
Even though you can inject an Injector wherever you'd like, you might find it easier to test if you simply inject a Provider<LookupService> instead. Only inject an Injector if you don't know what type you're going to need until runtime--for example, if you implement LookupService.lookup(...) using an Injector by passing the class literal to the injector to get an instance.
In fact, it's hard to say from here, but ILookup seems to act a lot like the Service Locator pattern, which solves the exact type of problem that Guice solves with dependency injection! If that's the case, you might as well rewrite ILookup to use Guice: Just remove calls to LookupService.instance().lookup(Foo.class) and instead create a matching pair of #Inject static Provider<Foo> fooProvider and requestStaticInjection(FooUser.class).
Hope that helps!

Resources