/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat Middleware LLC, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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
* http://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.jboss.seam.servlet;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.ProcessInjectionTarget;
import javax.enterprise.inject.spi.ProcessProducerMethod;
import javax.servlet.http.Cookie;
import org.jboss.seam.servlet.http.CookieParam;
import org.jboss.seam.servlet.http.CookieParamProducer;
import org.jboss.seam.servlet.http.HeaderParam;
import org.jboss.seam.servlet.http.HeaderParamProducer;
import org.jboss.seam.servlet.http.RequestParam;
import org.jboss.seam.servlet.http.RequestParamProducer;
import org.jboss.seam.servlet.http.TemporalConverters;
import org.jboss.seam.servlet.http.TypedParamValue;
import org.jboss.seam.servlet.http.literal.CookieParamLiteral;
import org.jboss.seam.servlet.http.literal.HeaderParamLiteral;
import org.jboss.seam.servlet.http.literal.RequestParamLiteral;
import org.jboss.seam.servlet.support.ServletMessages;
import org.jboss.seam.solder.bean.NarrowingBeanBuilder;
import org.jboss.seam.solder.literal.AnyLiteral;
import org.jboss.seam.solder.literal.DefaultLiteral;
import org.jboss.seam.solder.messages.Messages;
import org.jboss.seam.solder.reflection.PrimitiveTypes;
/**
* Generates producers to map to the type at an HTTP parameter injection point.
*
* @author <a href="http://community.jboss.org/people/dan.j.allen">Dan Allen</a>
*/
public class ServletExtension implements Extension {
private transient ServletMessages messages = Messages.getBundle(ServletMessages.class);
private final Map<Class<? extends Annotation>, TypedParamProducerBlueprint> producerBlueprints;
private final Map<Class<?>, Member> converterMembersByType;
public ServletExtension() {
producerBlueprints = new HashMap<Class<? extends Annotation>, TypedParamProducerBlueprint>();
producerBlueprints.put(RequestParam.class, new TypedParamProducerBlueprint(RequestParamLiteral.INSTANCE));
producerBlueprints.put(HeaderParam.class, new TypedParamProducerBlueprint(HeaderParamLiteral.INSTANCE));
producerBlueprints.put(CookieParam.class, new TypedParamProducerBlueprint(CookieParamLiteral.INSTANCE));
converterMembersByType = new HashMap<Class<?>, Member>();
}
public Member getConverterMember(Class<?> type) {
return converterMembersByType.get(type);
}
// according to the Java EE 6 javadoc (the authority according to the powers that be),
// this is the correct order of type parameters
void processRequestParamProducer(@Observes ProcessProducerMethod<Object, RequestParamProducer> event) {
if (event.getAnnotatedProducerMethod().getBaseType().equals(Object.class)
&& event.getAnnotatedProducerMethod().isAnnotationPresent(TypedParamValue.class)) {
producerBlueprints.get(RequestParam.class).setProducer(event.getBean());
}
}
// according to JSR-299 spec, this is the correct order of type parameters
@Deprecated
void processRequestParamProducerInverted(@Observes ProcessProducerMethod<RequestParamProducer, Object> event) {
if (isTypedParamProducer(event.getAnnotatedProducerMethod())) {
producerBlueprints.get(RequestParam.class).setProducer(event.getBean());
}
}
// according to the Java EE 6 javadoc (the authority according to the powers that be),
// this is the correct order of type parameters
void processHeaderParamProducer(@Observes ProcessProducerMethod<Object, HeaderParamProducer> event) {
if (isTypedParamProducer(event.getAnnotatedProducerMethod())) {
producerBlueprints.get(HeaderParam.class).setProducer(event.getBean());
}
}
// according to JSR-299 spec, this is the correct order of type parameters
@Deprecated
void processHeaderParamProducerInverted(@Observes ProcessProducerMethod<HeaderParamProducer, Object> event) {
if (isTypedParamProducer(event.getAnnotatedProducerMethod())) {
producerBlueprints.get(HeaderParam.class).setProducer(event.getBean());
}
}
// according to the Java EE 6 javadoc (the authority according to the powers that be),
// this is the correct order of type parameters
void processCookieParamProducer(@Observes ProcessProducerMethod<Object, CookieParamProducer> event) {
if (isTypedParamProducer(event.getAnnotatedProducerMethod())) {
producerBlueprints.get(CookieParam.class).setProducer(event.getBean());
}
}
// according to JSR-299 spec, this is the correct order of type parameters
@Deprecated
void processCookieParamProducerInverted(@Observes ProcessProducerMethod<CookieParamProducer, Object> event) {
if (isTypedParamProducer(event.getAnnotatedProducerMethod())) {
producerBlueprints.get(CookieParam.class).setProducer(event.getBean());
}
}
<X> void detectInjections(@Observes ProcessInjectionTarget<X> event) {
for (InjectionPoint ip : event.getInjectionTarget().getInjectionPoints()) {
Annotated annotated = ip.getAnnotated();
for (Class<? extends Annotation> paramAnnotationType : producerBlueprints.keySet()) {
if (annotated.isAnnotationPresent(paramAnnotationType)) {
Collection<Annotation> allowed = Arrays.asList(DefaultLiteral.INSTANCE, AnyLiteral.INSTANCE,
annotated.getAnnotation(paramAnnotationType));
boolean error = false;
for (Annotation q : ip.getQualifiers()) {
if (!allowed.contains(q)) {
event.addDefinitionError(new IllegalArgumentException(messages.additionalQualifiersNotPermitted(
paramAnnotationType.getSimpleName(), ip)));
error = true;
break;
}
}
if (error) {
break;
}
Type targetType = getActualBeanType(ip.getType());
if (!(targetType instanceof Class)) {
event.addDefinitionError(new IllegalArgumentException(messages.rawTypeRequired(
paramAnnotationType.getSimpleName(), ip)));
break;
}
try {
Class<?> targetClass = (Class<?>) targetType;
if (targetClass.equals(String.class)
|| (paramAnnotationType.equals(CookieParam.class) && targetClass.equals(Cookie.class))) {
// no converter needed
} else {
targetClass = PrimitiveTypes.box(targetClass);
Member converter = null;
if (targetClass.isEnum()) {
converter = targetClass.getMethod("valueOf", String.class);
} else if (Date.class.isAssignableFrom(targetClass)) {
converter = TemporalConverters.class.getMethod("parseDate", String.class);
} else if (Calendar.class.isAssignableFrom(targetClass)) {
converter = TemporalConverters.class.getMethod("parseCalendar", String.class);
} else {
try {
converter = targetClass.getConstructor(String.class);
} catch (NoSuchMethodException sce) {
converter = targetClass.getMethod("valueOf", String.class);
}
}
// TODO need way to register or detect custom converters
converterMembersByType.put(targetClass, converter);
}
producerBlueprints.get(paramAnnotationType).addTargetType(targetClass);
} catch (NoSuchMethodException nme) {
event.addDefinitionError(new IllegalArgumentException(messages.noConverterForType(
paramAnnotationType.getSimpleName(), ip)));
}
}
}
}
}
void installBeans(@Observes AfterBeanDiscovery event, BeanManager beanManager) {
for (TypedParamProducerBlueprint blueprint : producerBlueprints.values()) {
if (blueprint.getProducer() != null) {
for (Class<?> type : blueprint.getTargetTypes()) {
event.addBean(createTypedParamProducer(blueprint.getProducer(), type, blueprint.getQualifier(), beanManager));
}
}
}
producerBlueprints.clear();
}
private boolean isTypedParamProducer(AnnotatedMethod<?> method) {
return method.getBaseType().equals(Object.class) && method.isAnnotationPresent(TypedParamValue.class);
}
private <T> Bean<T> createTypedParamProducer(Bean<Object> delegate, Class<T> targetType, Annotation qualifier,
BeanManager beanManager) {
return new NarrowingBeanBuilder<T>(delegate, beanManager).readFromType(beanManager.createAnnotatedType(targetType))
.qualifiers(qualifier).create();
}
private Type getActualBeanType(Type t) {
if (t instanceof ParameterizedType && ((ParameterizedType) t).getRawType().equals(Instance.class)) {
return ((ParameterizedType) t).getActualTypeArguments()[0];
}
return t;
}
public static class TypedParamProducerBlueprint {
private Bean<Object> producer;
private Set<Class<?>> targetTypes;
private Annotation qualifier;
public TypedParamProducerBlueprint(Annotation qualifier) {
this.qualifier = qualifier;
targetTypes = new HashSet<Class<?>>();
}
public Bean<Object> getProducer() {
return producer;
}
// unchecked operation to support inverted observer type params
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setProducer(Bean producer) {
this.producer = producer;
}
public Set<Class<?>> getTargetTypes() {
return targetTypes;
}
public void addTargetType(Class<?> targetType) {
targetTypes.add(targetType);
}
public Annotation getQualifier() {
return qualifier;
}
}
}