org.omnifaces.cdi.param.ParamExtension Maven / Gradle / Ivy
/*
* Copyright OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.cdi.param;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toSet;
import static org.omnifaces.util.Reflection.modifyField;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashSet;
import java.util.Set;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.Annotated;
import jakarta.enterprise.inject.spi.AnnotatedConstructor;
import jakarta.enterprise.inject.spi.AnnotatedField;
import jakarta.enterprise.inject.spi.AnnotatedParameter;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.Extension;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.enterprise.inject.spi.InjectionTarget;
import jakarta.enterprise.inject.spi.ProcessInjectionTarget;
import jakarta.inject.Inject;
import jakarta.inject.Qualifier;
import org.omnifaces.cdi.InjectionTargetWrapper;
import org.omnifaces.cdi.Param;
import org.omnifaces.util.Beans;
/**
* CDI extension that works around the fact that CDI insists on doing absolutely
* guaranteed type safe injections. While generally applaudable this unfortunately
* impedes writing true generic producers that dynamically do conversion based on
* the target type.
*
* This extension collects the target types of each injection point qualified with
* the @
{@link Param} annotation and dynamically registers Beans that effectively
* represents producers for each type.
*
* Since OmniFaces 3.6, this extension also scans for @
{@link Param} without
* @
{@link Inject} and manually takes care of them while creating the bean.
*
* @since 2.0
* @author Arjan Tijms
*/
public class ParamExtension implements Extension {
private Set paramsWithInject = new HashSet<>();
/**
* Collect fields annotated with {@link Param}.
* @param The generic injection target type.
* @param event The process injection target event.
*/
public void collectParams(@Observes ProcessInjectionTarget event) {
Set> paramsWithoutInject = new HashSet<>();
for (AnnotatedField> field : event.getAnnotatedType().getFields()) {
collectParams(field, paramsWithInject, paramsWithoutInject);
}
for (AnnotatedConstructor> constructor : event.getAnnotatedType().getConstructors()) {
for (AnnotatedParameter> parameter : constructor.getParameters()) {
collectParams(parameter, paramsWithInject, null); // Without inject CDI won't invoke constructor in first place, so pass empty list.
}
}
processParamsWithoutInject(event, paramsWithoutInject);
}
private static void collectParams(Annotated annotated, Set paramsWithInject, Set> paramsWithoutInject) {
if (annotated.isAnnotationPresent(Param.class)) {
Type type = annotated.getBaseType();
if (type instanceof ParameterizedType && ParamValue.class.isAssignableFrom((Class>) ((ParameterizedType) type).getRawType())) {
return; // Skip ParamValue as it is already handled by RequestParameterProducer.
}
if (annotated.isAnnotationPresent(Inject.class) || (annotated instanceof AnnotatedParameter && ((AnnotatedParameter>) annotated).getDeclaringCallable().isAnnotationPresent(Inject.class))) {
paramsWithInject.add(type);
}
else if (annotated instanceof AnnotatedField) {
paramsWithoutInject.add((AnnotatedField>) annotated);
}
}
}
/**
* Process {@link Param} fields annotated with {@link Inject}.
* @param event The after bean discovery event.
*/
public void processParamsWithInject(@Observes AfterBeanDiscovery event) {
for (Type paramWithInject : paramsWithInject) {
event.addBean(new DynamicParamValueProducer(paramWithInject));
}
}
/**
/**
* Process {@link Param} fields without {@link Inject} annotation.
* @param The generic injection target type.
* @param event The process injection target event.
* @param paramsWithoutInject The {@link Param} fields without {@link Inject} annotation.
*/
public static void processParamsWithoutInject(ProcessInjectionTarget event, Set> paramsWithoutInject) {
if (!paramsWithoutInject.isEmpty()) {
event.setInjectionTarget(new ParamInjectionTarget<>(event.getInjectionTarget(), paramsWithoutInject));
}
}
private static final class ParamInjectionTarget extends InjectionTargetWrapper {
private Set> paramsWithoutInject = new HashSet<>();
public ParamInjectionTarget(InjectionTarget wrapped, Set> paramsWithoutInject) {
super(wrapped);
this.paramsWithoutInject = paramsWithoutInject;
}
@Override
public void inject(T instance, CreationalContext ctx) {
Class> beanClass = Beans.unwrapIfNecessary(instance.getClass());
for (AnnotatedField> paramWithoutInject : paramsWithoutInject) {
ParamValue> paramValue = new ParamProducer().produce(new ParamInjectionPoint(beanClass, paramWithoutInject));
Field field = paramWithoutInject.getJavaMember();
modifyField(instance, field, paramValue.getValue());
}
super.inject(instance, ctx);
}
private static final class ParamInjectionPoint implements InjectionPoint {
private Bean> bean;
private AnnotatedField> paramWithoutInject;
public ParamInjectionPoint(Class> beanClass, AnnotatedField> paramWithoutInject) {
this.bean = Beans.resolve(beanClass);
this.paramWithoutInject = paramWithoutInject;
}
@Override
public Type getType() {
return paramWithoutInject.getBaseType();
}
@Override
public Set getQualifiers() {
return stream(paramWithoutInject.getJavaMember().getAnnotations()).filter(annotation -> annotation.annotationType().isAnnotationPresent(Qualifier.class)).collect(toSet());
}
@Override
public Bean> getBean() {
return bean;
}
@Override
public Member getMember() {
return paramWithoutInject.getJavaMember();
}
@Override
public Annotated getAnnotated() {
return paramWithoutInject;
}
@Override
public boolean isDelegate() {
return true;
}
@Override
public boolean isTransient() {
return true;
}
}
}
}