Repository: deltaspike Updated Branches: refs/heads/master ada7480c8 -> bbd5fa8b2
DELTASPIKE-1015 support for hierarchic BeanManager This is mainly needed in EARs. Using WeakReferences will prevent mem leaks if a webapp using DS get's undeployed. Otherwise the JVM will not be able to garbage collect any WebAppClassLoader Project: http://git-wip-us.apache.org/repos/asf/deltaspike/repo Commit: http://git-wip-us.apache.org/repos/asf/deltaspike/commit/bbd5fa8b Tree: http://git-wip-us.apache.org/repos/asf/deltaspike/tree/bbd5fa8b Diff: http://git-wip-us.apache.org/repos/asf/deltaspike/diff/bbd5fa8b Branch: refs/heads/master Commit: bbd5fa8b24a08f0c0cda8e0dca61403872a00594 Parents: ada7480 Author: Mark Struberg <[email protected]> Authored: Mon Nov 23 10:21:30 2015 +0100 Committer: Mark Struberg <[email protected]> Committed: Mon Nov 23 10:31:05 2015 +0100 ---------------------------------------------------------------------- .../core/util/ParentExtensionStorage.java | 139 +++++++ .../impl/message/MessageBundleExtension.java | 417 ++++++++++--------- .../impl/extension/SecurityExtension.java | 14 + 3 files changed, 369 insertions(+), 201 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/deltaspike/blob/bbd5fa8b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ParentExtensionStorage.java ---------------------------------------------------------------------- diff --git a/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ParentExtensionStorage.java b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ParentExtensionStorage.java new file mode 100644 index 0000000..6e1f044 --- /dev/null +++ b/deltaspike/core/api/src/main/java/org/apache/deltaspike/core/util/ParentExtensionStorage.java @@ -0,0 +1,139 @@ +/* + * 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.util; + +import javax.enterprise.inject.spi.Extension; + +import java.lang.ref.WeakReference; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Support for Containers with 'hierarchic BeanManagers' + * This is mostly useful for EAR applications. + * + * Some EE Container scan the common shared EAR lib path and reuse this information + * for the webapps in the EAR. This is actually the only approach a container can + * do to prevent mem leaks and side effects spreading to different webapps. + * Of course this also means that the webapps get their own (different) + * instances of an Extension. + * + * To acknowledge this solution we provide a mechanism to lookup 'parent Extensions' + * which is very similar to handling parent ClassLoaders. + * + * All your Extension has to do is to register itself in + * {@link javax.enterprise.inject.spi.BeforeBeanDiscovery}. + * Later at boot time the Extension can lookup it's parent Extension instance and + * e.g. check which classes got scanned in the parent ClassLoader. + * + * The ExtensionInfo automatically gets removed if the webapp gets undeployed. + */ +public final class ParentExtensionStorage +{ + + private static Set<ExtensionStorageInfo> extensionStorage = new HashSet<ExtensionStorageInfo>(); + + private ParentExtensionStorage() + { + // utility class ct + } + + /** + * Add info about an Extension to our storage + * This method is usually called during boostrap via {@code @Observes BeforeBeanDiscovery}. + */ + public static synchronized void addExtension(Extension extension) + { + removeAbandonedExtensions(); + + ClassLoader classLoader = ClassUtils.getClassLoader(null); + extensionStorage.add(new ExtensionStorageInfo(classLoader, extension)); + } + + /** + * When adding a new Extension we also clean up ExtensionInfos + * from ClassLoaders which got unloaded. + */ + private static void removeAbandonedExtensions() + { + Iterator<ExtensionStorageInfo> it = extensionStorage.iterator(); + while (it.hasNext()) + { + ExtensionStorageInfo info = it.next(); + if (info.isAbandoned()) + { + it.remove(); + } + } + } + + /** + * @return the Extension from the same type but registered in a hierarchic 'parent' BeanManager + */ + public static synchronized <T extends Extension> T getParentExtension(Extension extension) + { + ClassLoader parentClassLoader = ClassUtils.getClassLoader(null).getParent(); + + Iterator<ExtensionStorageInfo> extIt = extensionStorage.iterator(); + while (extIt.hasNext()) + { + ExtensionStorageInfo extensionInfo = extIt.next(); + if (!extensionInfo.isAbandoned() && // weak reference case + extension.getClass().equals(extensionInfo.getExtension().getClass()) && + extensionInfo.getClassLoader().equals(parentClassLoader)) + { + return (T) extensionInfo.getExtension(); + } + } + return null; + } + + + /** + * Information about an Extension instance and in which classloader it got used + */ + private static class ExtensionStorageInfo + { + // we use WeakReferences to allow perfect unloading of any webapp ClassLoader + private final WeakReference<ClassLoader> classLoader; + private final WeakReference<Extension> extension; + + public ExtensionStorageInfo(ClassLoader classLoader, Extension extension) + { + this.classLoader = new WeakReference<ClassLoader>(classLoader); + this.extension = new WeakReference<Extension>(extension); + } + + boolean isAbandoned() + { + return classLoader.get() == null || extension.get() == null; + } + + ClassLoader getClassLoader() + { + return classLoader.get(); + } + + Extension getExtension() + { + return extension.get(); + } + } +} http://git-wip-us.apache.org/repos/asf/deltaspike/blob/bbd5fa8b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/message/MessageBundleExtension.java ---------------------------------------------------------------------- diff --git a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/message/MessageBundleExtension.java b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/message/MessageBundleExtension.java index 5636529..1733f5f 100644 --- a/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/message/MessageBundleExtension.java +++ b/deltaspike/core/impl/src/main/java/org/apache/deltaspike/core/impl/message/MessageBundleExtension.java @@ -1,201 +1,216 @@ -/* - * 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.*; - -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.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()); - } - - @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; - } - - 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)); - } - - } -} +/* + * 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)); + } + + } +} http://git-wip-us.apache.org/repos/asf/deltaspike/blob/bbd5fa8b/deltaspike/modules/security/impl/src/main/java/org/apache/deltaspike/security/impl/extension/SecurityExtension.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/security/impl/src/main/java/org/apache/deltaspike/security/impl/extension/SecurityExtension.java b/deltaspike/modules/security/impl/src/main/java/org/apache/deltaspike/security/impl/extension/SecurityExtension.java index 375def0..95f0fc5 100644 --- a/deltaspike/modules/security/impl/src/main/java/org/apache/deltaspike/security/impl/extension/SecurityExtension.java +++ b/deltaspike/modules/security/impl/src/main/java/org/apache/deltaspike/security/impl/extension/SecurityExtension.java @@ -21,6 +21,7 @@ package org.apache.deltaspike.security.impl.extension; import org.apache.deltaspike.core.spi.activation.Deactivatable; import org.apache.deltaspike.core.util.ClassDeactivationUtils; +import org.apache.deltaspike.core.util.ParentExtensionStorage; import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder; import org.apache.deltaspike.security.api.authorization.Secures; import org.apache.deltaspike.security.api.authorization.SecurityDefinitionException; @@ -56,6 +57,7 @@ public class SecurityExtension implements Extension, Deactivatable { isActivated = ClassDeactivationUtils.isActivated(getClass()); securityMetaDataStorage = new SecurityMetaDataStorage(); + ParentExtensionStorage.addExtension(this); } //workaround for OWB @@ -136,6 +138,18 @@ public class SecurityExtension implements Extension, Deactivatable } SecurityMetaDataStorage metaDataStorage = getMetaDataStorage(); + + SecurityExtension parentExtension = ParentExtensionStorage.getParentExtension(this); + if (parentExtension != null) + { + // also add the authorizers from the parent extension + Set<Authorizer> parentAuthorizers = parentExtension.getMetaDataStorage().getAuthorizers(); + for (Authorizer parentAuthorizer : parentAuthorizers) + { + metaDataStorage.addAuthorizer(parentAuthorizer); + } + } + metaDataStorage.registerSecuredMethods(); for (final AnnotatedMethod<?> method : metaDataStorage.getSecuredMethods())
