/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.deltaspike.core.impl.message; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.AfterBeanDiscovery; import javax.enterprise.inject.spi.AfterDeploymentValidation; import javax.enterprise.inject.spi.AnnotatedType; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeforeBeanDiscovery; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; import org.apache.deltaspike.core.api.literal.DefaultLiteral; import org.apache.deltaspike.core.api.message.Message; import org.apache.deltaspike.core.api.message.MessageBundle; import org.apache.deltaspike.core.api.message.MessageTemplate; import org.apache.deltaspike.core.api.provider.BeanProvider; import org.apache.deltaspike.core.api.provider.DependentProvider; import org.apache.deltaspike.core.util.ClassUtils; import org.apache.deltaspike.core.util.ParentExtensionStorage; import org.apache.deltaspike.core.util.bean.BeanBuilder; import org.apache.deltaspike.core.spi.activation.Deactivatable; import org.apache.deltaspike.core.util.ClassDeactivationUtils; import org.apache.deltaspike.core.util.metadata.builder.ContextualLifecycle; /** * Extension for handling {@link MessageBundle}s. * * @see MessageBundle * @see MessageTemplate */ public class MessageBundleExtension implements Extension, Deactivatable { private final Collection<AnnotatedType<?>> messageBundleTypes = new HashSet<AnnotatedType<?>>(); private List<String> deploymentErrors = new ArrayList<String>(); private Boolean isActivated = true; @SuppressWarnings("UnusedDeclaration") protected void init(@Observes BeforeBeanDiscovery beforeBeanDiscovery) { isActivated = ClassDeactivationUtils.isActivated(getClass()); ParentExtensionStorage.addExtension(this); } @SuppressWarnings("UnusedDeclaration") protected void detectInterfaces(@Observes ProcessAnnotatedType processAnnotatedType) { if (!isActivated) { return; } AnnotatedType<?> type = processAnnotatedType.getAnnotatedType(); if (type.isAnnotationPresent(MessageBundle.class)) { if (validateMessageBundle(type.getJavaClass())) { messageBundleTypes.add(type); } } } /** * @return <code>true</code> if all is well */ private boolean validateMessageBundle(Class<?> currentClass) { boolean ok = true; // sanity check: annotated class must be an Interface if (!currentClass.isInterface()) { deploymentErrors.add("@MessageBundle must only be used on Interfaces, but got used on class " + currentClass.getName()); return false; } for (Method currentMethod : currentClass.getDeclaredMethods()) { if (!currentMethod.isAnnotationPresent(MessageTemplate.class)) { continue; } if (String.class.isAssignableFrom(currentMethod.getReturnType())) { continue; } if (Message.class.isAssignableFrom(currentMethod.getReturnType())) { continue; } deploymentErrors.add(currentMethod.getReturnType().getName() + " isn't supported. Details: " + currentMethod.getDeclaringClass().getName() + "#" + currentMethod.getName() + " only " + String.class.getName() + " or " + Message.class.getName()); ok = false; } return ok; } @SuppressWarnings("UnusedDeclaration") protected void installMessageBundleProducerBeans(@Observes AfterBeanDiscovery abd, BeanManager beanManager) { if (!deploymentErrors.isEmpty()) { abd.addDefinitionError(new IllegalArgumentException("The following MessageBundle problems where found: " + Arrays.toString(deploymentErrors.toArray()))); return; } MessageBundleExtension parentExtension = ParentExtensionStorage.getParentExtension(this); if (parentExtension != null) { messageBundleTypes.addAll(parentExtension.messageBundleTypes); } for (AnnotatedType<?> type : messageBundleTypes) { abd.addBean(createMessageBundleBean(type, beanManager)); } } private <T> Bean<T> createMessageBundleBean(AnnotatedType<T> annotatedType, BeanManager beanManager) { BeanBuilder<T> beanBuilder = new BeanBuilder<T>(beanManager).readFromType(annotatedType); beanBuilder.beanLifecycle(new MessageBundleLifecycle<T>(beanManager)); beanBuilder.types(annotatedType.getJavaClass(), Object.class, Serializable.class); beanBuilder.addQualifier(new DefaultLiteral()); beanBuilder.passivationCapable(true); beanBuilder.scope(ApplicationScoped.class); // needs to be a normalscope due to a bug in older Weld versions beanBuilder.id("MessageBundleBean#" + annotatedType.getJavaClass().getName()); return beanBuilder.create(); } @SuppressWarnings("UnusedDeclaration") protected void cleanup(@Observes AfterDeploymentValidation afterDeploymentValidation) { messageBundleTypes.clear(); } private static class MessageBundleLifecycle<T> implements ContextualLifecycle<T> { private final BeanManager beanManager; private DependentProvider<MessageBundleInvocationHandler> invocationHandlerProvider; private MessageBundleLifecycle(BeanManager beanManager) { this.beanManager = beanManager; } @Override public T create(Bean<T> bean, CreationalContext<T> creationalContext) { invocationHandlerProvider = BeanProvider.getDependent(beanManager, MessageBundleInvocationHandler.class); return createMessageBundleProxy((Class<T>) bean.getBeanClass(), invocationHandlerProvider.get()); } @Override public void destroy(Bean<T> bean, T instance, CreationalContext<T> creationalContext) { if (invocationHandlerProvider != null) { invocationHandlerProvider.destroy(); } } private <T> T createMessageBundleProxy(Class<T> type, MessageBundleInvocationHandler handler) { return type.cast(Proxy.newProxyInstance(ClassUtils.getClassLoader(null), new Class<?>[]{type, Serializable.class}, handler)); } } }