Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
new version of Autowire with CDI in mind
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosmunoz committed Feb 28, 2016
1 parent 5374e20 commit ddae74d
Show file tree
Hide file tree
Showing 11 changed files with 728 additions and 41 deletions.
70 changes: 70 additions & 0 deletions zanata-war/src/test/java/org/zanata/cdi/AutowireUtils.java
@@ -0,0 +1,70 @@
/*
* Copyright 2010, Red Hat, Inc. and individual contributors as indicated by the

This comment has been minimized.

Copy link
@seanf

seanf Feb 29, 2016

Contributor

Let's hope the world doesn't end in 2012, eh?

This comment has been minimized.

Copy link
@carlosmunoz

carlosmunoz Feb 29, 2016

Author Member

Ha! Remnants of my old settings in the IDE config. There are also going to be some tabs around the code too 😞

* @author tags. See the copyright.txt file in the distribution for a full
* listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.zanata.cdi;

import com.google.inject.Binder;
import com.google.inject.TypeLiteral;
import org.zanata.events.AlreadyLoggedInEvent;
import org.zanata.events.LoginFailedEvent;
import org.zanata.events.LoginSuccessfulEvent;
import org.zanata.events.LogoutEvent;
import org.zanata.events.NotLoggedInEvent;

import javax.enterprise.event.Event;
import java.lang.annotation.Annotation;
import java.util.function.Consumer;

/**
* @author Carlos Munoz <a href="mailto:camunoz@redhat.com">camunoz@redhat.com</a>
*/
public final class AutowireUtils {

private static final Consumer<Binder> MOCK_EVENTS = binder -> {
binder.bind(new TypeLiteral<Event<AlreadyLoggedInEvent>>(){}).toProvider(
MockProvider.of(new TypeLiteral<Event<AlreadyLoggedInEvent>>(){}));
binder.bind(new TypeLiteral<Event<LoginFailedEvent>>(){}).toProvider(
MockProvider.of(new TypeLiteral<Event<LoginFailedEvent>>(){}));
binder.bind(new TypeLiteral<Event<LoginSuccessfulEvent>>(){}).toProvider(
MockProvider.of(new TypeLiteral<Event<LoginSuccessfulEvent>>(){}));
binder.bind(new TypeLiteral<Event<LogoutEvent>>(){}).toProvider(
MockProvider.of(new TypeLiteral<Event<LogoutEvent>>(){}));
binder.bind(new TypeLiteral<Event<NotLoggedInEvent>>(){}).toProvider(
MockProvider.of(new TypeLiteral<Event<NotLoggedInEvent>>(){}));

This comment has been minimized.

Copy link
@seanf

seanf Feb 29, 2016

Contributor

Any way we can avoid the need to list every event type?

This comment has been minimized.

Copy link
@carlosmunoz

carlosmunoz Feb 29, 2016

Author Member

none that I could find. That's why I put them in a common place.

This comment has been minimized.

Copy link
@seanf

seanf Feb 29, 2016

Contributor

What about this?

binder.bind(Event.class).toProvider(MockProvider.of(Event.class));

As suggested here: https://stackoverflow.com/a/10846095/14379

(Even that seems a bit redundant, because Event.class is repeated.)

Is there a test which uses events we could try? EDIT: FileServiceTest

This comment has been minimized.

Copy link
@seanf

seanf via email Feb 29, 2016

Contributor
};

private AutowireUtils() {
}

public static void mockInstances(Class cls, Binder binder) {
binder.bind(cls).toProvider(MockProvider.of(cls));
}

public static void mockInstances(Class cls,
Class<? extends Annotation> annotatedWith, Binder binder) {
binder.bind(cls).annotatedWith(annotatedWith)

This comment has been minimized.

Copy link
@seanf

seanf Feb 29, 2016

Contributor

This line is hard to read. I suggest renaming annotatedWith to annotationClass or similar. Perhaps rename cls to beanClass too?

.toProvider(MockProvider.of(cls));
}

public static Consumer<Binder> mockEvents() {
return MOCK_EVENTS;
}

}
161 changes: 161 additions & 0 deletions zanata-war/src/test/java/org/zanata/cdi/CDIAutowire.java
@@ -0,0 +1,161 @@
/*
* Copyright 2010, Red Hat, Inc. and individual contributors as indicated by the
* @author tags. See the copyright.txt file in the distribution for a full
* listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.zanata.cdi;

import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import lombok.extern.slf4j.Slf4j;
import org.apache.deltaspike.core.api.exclude.Exclude;
import org.apache.deltaspike.core.api.projectstage.ProjectStage;
import org.mockito.Mockito;
import org.zanata.seam.AutowireException;
import org.zanata.util.CDIAutowireLocator;
import org.zanata.util.IServiceLocator;

import javax.enterprise.context.Dependent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.Consumer;

/**
* @author Carlos Munoz <a href="mailto:camunoz@redhat.com">camunoz@redhat.com</a>
*/
@Slf4j
@Exclude(ifProjectStage = ProjectStage.IntegrationTest.class)
public class CDIAutowire {

private Injector injector;
private Consumer<Binder> binderConfiguration;

private final Consumer<Binder> staticConfig = binder -> {
// add mandatory bindings
// Service Locator
binder.bind(IServiceLocator.class).toInstance(new CDIAutowireLocator(this));
// Invoke @PostConstruct methods
binder.bindListener(new AbstractMatcher<TypeLiteral<?>>() {
@Override
public boolean matches(TypeLiteral<?> typeLiteral) {
return true; // check all components
}
}, new TypeListener() {
@Override
public <I> void hear(TypeLiteral<I> type,
TypeEncounter<I> encounter) {
encounter.register((InjectionListener<I>) injectee -> {
invokePostConstructMethod(injectee);
});
}
});
// Bind the dependent scope
// TODO Other scopes?
binder.bindScope(Dependent.class, Scopes.NO_SCOPE);
};

public final CDIAutowire configure(Consumer<Binder> configure) {
composeBinderConfiguration(configure);
return this;
}

public Injector getInjector() {
if(injector == null) {
injector = createInjector();
}
return injector;
}

private void composeBinderConfiguration(Consumer<Binder> postConsumer) {
if(binderConfiguration == null) {
binderConfiguration = postConsumer;
} else {
binderConfiguration = binderConfiguration.andThen(postConsumer);
}
}

private Injector createInjector() {
Consumer<Binder> fullConfig = binderConfiguration.andThen(staticConfig);
// Build a regular module which runs the statically provided configuration
Module originalModule = new AbstractModule() {
@Override
protected void configure() {
fullConfig.accept(binder());
}
};

return Guice.createInjector(originalModule);
}

private static void bindMissingDependency(Binder binder,
InjectionErrorExtractor.MissingDependency dep) {
Class dependencyType = dep.getDependencyType();
// binf the missing dependency
AnnotatedBindingBuilder builder =
binder.bind(dependencyType);
// If there is a provided annotation qualifier
if(dep.getAnnotation() != null) {
builder.annotatedWith(dep.getAnnotation());
}
// Special case: Strings
if(String.class.equals(dependencyType)) {
builder.toInstance("Mock value (please bind)");
} else {
builder.toInstance(Mockito.mock(dependencyType));
}

}

/**
* Invokes a single method (the first found) annotated with
* {@link javax.annotation.PostConstruct},
*/
private static void invokePostConstructMethod(Object bean) {
Class<?> compClass = bean.getClass();
boolean postConstructAlreadyFound = false;

for (Method m : compClass.getDeclaredMethods()) {
// Per the spec, there should be only one PostConstruct method
if (m.getAnnotation(javax.annotation.PostConstruct.class) != null) {
if (postConstructAlreadyFound) {
throw new AutowireException("More than one PostConstruct method found for class "
+ compClass.getName());
}
try {
m.setAccessible(true);
m.invoke(bean); // there should be no params
postConstructAlreadyFound = true;
} catch (IllegalAccessException | InvocationTargetException e) {
throw new AutowireException(
"Error invoking PostConstruct method in bean of class "
+ compClass.getName(), e);
}
}
}
}
}
109 changes: 109 additions & 0 deletions zanata-war/src/test/java/org/zanata/cdi/CDIAutowireLocator.java
@@ -0,0 +1,109 @@
/*
* Copyright 2010, Red Hat, Inc. and individual contributors as indicated by the
* @author tags. See the copyright.txt file in the distribution for a full
* listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.zanata.util;


import java.lang.annotation.Annotation;
import java.util.Optional;

import javax.enterprise.inject.Alternative;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import com.google.inject.Injector;
import org.apache.deltaspike.core.api.exclude.Exclude;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zanata.cdi.CDIAutowire;
import org.zanata.seam.SeamAutowire;

/**
* Replacement class for our ServiceLocator. Tests that use
* the {@link org.zanata.cdi.CDIAutowire} class will use this class instead of
* the real one ({@Link org.zanata.util.ServiceLocator}) to request components.
*
* @author Carlos Munoz <a
* href="mailto:camunoz@redhat.com">camunoz@redhat.com</a>
*/
@Alternative
@Exclude
public class CDIAutowireLocator implements IServiceLocator {
private static final Logger log =
LoggerFactory.getLogger(AutowireLocator.class);

private final CDIAutowire autowire;

public CDIAutowireLocator() {
this(new CDIAutowire());
}

public CDIAutowireLocator(CDIAutowire autowire) {
this.autowire = autowire;
}

public <T> BeanHolder<T> getDependent(Class<T> clazz, Annotation... qualifiers) {
return new BeanHolder<T>(getInstance(clazz, qualifiers));
}

public <T> T getInstance(Class<T> clazz, Annotation... qualifiers) {
// try {
// if (!SeamAutowire.disableRealServiceLocator) {
// T bean = ServiceLocator.INSTANCE.getInstance(clazz,
// qualifiers);
// log.debug("Returning CDI bean: {}", bean);
// return bean;
// }
// } catch (IllegalStateException e) {
// log.debug("Can't find CDI bean, trying SeamAutowire",
// e.getMessage());
// }
// TODO Figure out how to do qualifiers
return autowire.getInjector().getInstance(clazz);
}

public <T> Optional<T> getOptionalInstance(Class<T> clazz, Annotation... qualifiers) {
// TODO Figure out how to do qualifiers
return Optional.ofNullable(autowire.getInjector().getInstance(clazz));
}

@Override
public EntityManager getEntityManager() {
return getInstance(EntityManager.class);
}

@Override
public EntityManagerFactory getEntityManagerFactory() {
return getInstance(EntityManagerFactory.class);
}

@Override
public <T> T getJndiComponent(String jndiName, Class<T> clazz)
throws NamingException {
T instance = (T) SeamAutowire.instance().getComponent(jndiName);

This comment has been minimized.

Copy link
@seanf

seanf Feb 29, 2016

Contributor

Wait, what?

This comment has been minimized.

Copy link
@carlosmunoz

carlosmunoz Feb 29, 2016

Author Member

not fully baked... it's still a copy of the old autowire code.

if (instance == null) {
throw new NamingException("component with JNDI name " + jndiName +
" has not been registered with Autowire");
}
return instance;
}

}

1 comment on commit ddae74d

@seanf
Copy link
Contributor

@seanf seanf commented on ddae74d Feb 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to look at the class org.jglue.cdiunit.internal.WeldTestUrlDeployment in CDI-Unit. It does most of the work in CDI-Unit, creating a deployment context for Weld SE, a bit like building up a Guice module and its bindings.

I guess we could always use something like org.atteo.classindex.processor.ClassIndexProcessor to index the package org.zanata.events. Any solution is going to be a bit toe-curling if Guice is fighting us though.

Please sign in to comment.