diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/bootstrap/ManagementBootstrap.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/bootstrap/ManagementBootstrap.java new file mode 100644 index 0000000..c7f51d9 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/bootstrap/ManagementBootstrap.java @@ -0,0 +1,70 @@ +package br.gov.frameworkdemoiselle.internal.bootstrap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +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.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; + +import br.gov.frameworkdemoiselle.internal.context.ContextManager; +import br.gov.frameworkdemoiselle.internal.context.ManagedContext; +import br.gov.frameworkdemoiselle.lifecycle.AfterShutdownProccess; +import br.gov.frameworkdemoiselle.management.annotation.Managed; +import br.gov.frameworkdemoiselle.management.extension.ManagementExtension; +import br.gov.frameworkdemoiselle.management.internal.ManagedType; +import br.gov.frameworkdemoiselle.management.internal.MonitoringManager; +import br.gov.frameworkdemoiselle.util.Beans; + +public class ManagementBootstrap implements Extension { + + protected static List> types = Collections.synchronizedList(new ArrayList>()); + + private List> managementExtensionCache = Collections.synchronizedList(new ArrayList>()); + + + public void detectAnnotation(@Observes final ProcessAnnotatedType event, final BeanManager beanManager) { + if (event.getAnnotatedType().isAnnotationPresent(Managed.class)) { + types.add(event.getAnnotatedType()); + } + } + + public void activateContexts(@Observes final AfterBeanDiscovery event) { + ContextManager.initialize(event); + ContextManager.add(new ManagedContext(), event); + } + + @SuppressWarnings("unchecked") + public void registerAvailableManagedTypes(@Observes final AfterDeploymentValidation event,BeanManager beanManager) { + MonitoringManager monitoringManager = Beans.getReference(MonitoringManager.class); + for (AnnotatedType type : types) { + ManagedType managedType = new ManagedType(type.getJavaClass()); + monitoringManager.addManagedType(managedType); + } + + Set> extensionBeans = beanManager.getBeans(ManagementExtension.class); + if (extensionBeans!=null){ + for (Bean bean : extensionBeans){ + Class extensionConcreteClass = bean.getBeanClass(); + managementExtensionCache.add((Class) extensionConcreteClass); + } + } + + monitoringManager.initialize(managementExtensionCache); + } + + public void unregisterAvailableManagedTypes(@Observes final AfterShutdownProccess event) { + + MonitoringManager manager = Beans.getReference(MonitoringManager.class); + manager.shutdown(managementExtensionCache); + + } + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/AbstractCustomContext.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/AbstractCustomContext.java index 3be8fda..e0f0f19 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/AbstractCustomContext.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/AbstractCustomContext.java @@ -52,9 +52,9 @@ public abstract class AbstractCustomContext implements CustomContext { private final Class scope; - public AbstractCustomContext(final Class scope, boolean active) { + public AbstractCustomContext(final Class scope) { this.scope = scope; - this.active = active; + this.active = false; } protected abstract Store getStore(); diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ContextManager.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ContextManager.java index 79b7931..a3d1aa8 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ContextManager.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ContextManager.java @@ -4,6 +4,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import javax.enterprise.context.ContextNotActiveException; import javax.enterprise.context.spi.Context; @@ -36,15 +37,13 @@ import br.gov.frameworkdemoiselle.util.ResourceBundle; */ public class ContextManager { - private static List contexts = Collections.synchronizedList(new ArrayList()); - - private static List activatedCustomContexts = Collections.synchronizedList(new ArrayList()); + private static List contexts = Collections.synchronizedList(new ArrayList()); private static boolean initialized = false; - private static ResourceBundle bundle = ResourceBundleProducer.create("demoiselle-core-bundle"); + private static ResourceBundle bundle; - private static Logger logger = LoggerProducer.create(ContextManager.class); + private static Logger logger; /** *

Initializes this manager and adds the {@link StaticContext} context to the list of managed contexts. Other @@ -75,8 +74,15 @@ public class ContextManager { * @param event The CDI event indicating all beans have been discovered. */ public static void add(CustomContext context,AfterBeanDiscovery event){ - context.setActive(true); + for (CustomContextCounter contextCounter : contexts){ + if (contextCounter.isSame(context.getClass(), context.getScope())){ + return; + } + } + + context.setActive(false); event.addContext(context); + contexts.add(new CustomContextCounter(context)); } /** @@ -98,17 +104,15 @@ public class ContextManager { * * @throws DemoiselleException if there is no managed context of the provided type and scope. */ - public static boolean activate(Class customContextClass , Class scope){ + public static synchronized void activate(Class customContextClass , Class scope){ if (!initialized){ throw new DemoiselleException(getBundle().getString("custom-context-manager-not-initialized")); } - for (CustomContext context : contexts){ - if (context.getClass().getCanonicalName().equals( customContextClass.getCanonicalName() ) - && context.getScope().equals(scope)){ - if (!context.isActive()){ - return activate(context); - } + for (CustomContextCounter context : contexts){ + if ( context.isSame(customContextClass, scope) ){ + context.activate(); + return; } } @@ -135,66 +139,99 @@ public class ContextManager { * * @throws DemoiselleException if there is no managed context of the provided type and scope. */ - public static boolean deactivate(Class customContextClass,Class scope){ + public static synchronized void deactivate(Class customContextClass,Class scope){ if (!initialized){ throw new DemoiselleException(getBundle().getString("custom-context-manager-not-initialized")); } - for (CustomContext context : activatedCustomContexts){ - if (context.getClass().getCanonicalName().equals( customContextClass.getCanonicalName() ) - && context.getScope().equals(scope)){ - - if (context.isActive()){ - return deactivate(context); - } + for (CustomContextCounter context : contexts){ + if (context.isSame(customContextClass, scope)){ + context.deactivate(); + return; } } throw new DemoiselleException(getBundle().getString("custom-context-not-found",customContextClass.getCanonicalName(),scope.getSimpleName())); } - - private static boolean activate(CustomContext context){ - try{ - Beans.getBeanManager().getContext(context.getScope()); - return false; - } - catch(ContextNotActiveException ce){ - context.setActive(true); - activatedCustomContexts.add(context); - getLogger().trace(getBundle().getString("custom-context-was-activated", context.getClass().getCanonicalName(),context.getScope().getCanonicalName())); - return true; + + static Logger getLogger(){ + if (logger==null){ + logger = LoggerProducer.create(ContextManager.class); } + + return logger; } - private static boolean deactivate(CustomContext context){ - try{ - Context activeContext = Beans.getBeanManager().getContext(context.getScope()); - if (activeContext.equals(context)){ - context.setActive(false); - activatedCustomContexts.remove(context); - return true; - } + static ResourceBundle getBundle(){ + if (bundle==null){ + bundle = ResourceBundleProducer.create("demoiselle-core-bundle",Locale.getDefault()); } - catch(ContextNotActiveException e){ + + return bundle; + } +} +/** + * Class that counts how many attemps to activate and deactivate this context received, avoiding cases + * where one client activates given context and another one deactivates it, leaving the first client + * with no active context before completion. + * + * @author serpro + * + */ +class CustomContextCounter{ + private CustomContext context; + private int activationCounter = 0; + + public CustomContextCounter(CustomContext customContext) { + this.context = customContext; + } + + public boolean isSame(Class customContextClass,Class scope){ + if (context.getClass().getCanonicalName().equals( customContextClass.getCanonicalName() ) + && context.getScope().equals(scope)){ + return true; } return false; } - private static Logger getLogger(){ - if (logger==null){ - logger = LoggerProducer.create(ContextManager.class); + public CustomContext getInternalContext(){ + return this.context; + } + + public synchronized void activate(){ + try{ + Context c = Beans.getBeanManager().getContext(context.getScope()); + if (c==context){ + activationCounter++; + } + } + catch(ContextNotActiveException ce){ + context.setActive(true); + activationCounter++; + ContextManager.getLogger().trace( + ContextManager.getBundle().getString("custom-context-was-activated" + , context.getClass().getCanonicalName() + ,context.getScope().getCanonicalName() + )); } - - return logger; } - private static ResourceBundle getBundle(){ - if (bundle==null){ - bundle = ResourceBundleProducer.create("demoiselle-core-bundle"); + public synchronized void deactivate(){ + try{ + Context c = Beans.getBeanManager().getContext(context.getScope()); + if (c==context){ + activationCounter--; + if (activationCounter==0){ + context.setActive(false); + ContextManager.getLogger().trace( + ContextManager.getBundle().getString("custom-context-was-deactivated" + , context.getClass().getCanonicalName() + ,context.getScope().getCanonicalName() + )); + } + } } - - return bundle; + catch(ContextNotActiveException ce){} } - } diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ManagedContext.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ManagedContext.java new file mode 100644 index 0000000..00873cc --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ManagedContext.java @@ -0,0 +1,23 @@ +package br.gov.frameworkdemoiselle.internal.context; + +import javax.enterprise.context.RequestScoped; + +import br.gov.frameworkdemoiselle.management.annotation.Managed; + +/** + * Context that stores {@link RequestScoped} beans during client calls to {@link Managed} classes. + * This context is only activated when no other context is active for {@link RequestScoped}. + * + * @author serpro + * + */ +public class ManagedContext extends ThreadLocalContext { + + /** + * Constructs a new context. + */ + public ManagedContext() { + super(RequestScoped.class); + } + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/StaticContext.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/StaticContext.java index d1681c5..d8f02fd 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/StaticContext.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/StaticContext.java @@ -43,7 +43,7 @@ public class StaticContext extends AbstractCustomContext { private final static Store store = createStore(); public StaticContext() { - super(StaticScoped.class, true); + super(StaticScoped.class); } @Override diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ThreadLocalContext.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ThreadLocalContext.java index 9bcf461..11ac567 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ThreadLocalContext.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/context/ThreadLocalContext.java @@ -50,16 +50,18 @@ package br.gov.frameworkdemoiselle.internal.context; import java.lang.annotation.Annotation; +/** + * Base context that has a separated store for each thread + * + * @author serpro + * + */ public class ThreadLocalContext extends AbstractCustomContext { private final ThreadLocal threadLocal = new ThreadLocal(); public ThreadLocalContext(final Class scope) { - super(scope, true); - } - - public ThreadLocalContext(final Class scope, boolean active) { - super(scope, active); + super(scope); } @Override diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationMapValueExtractor.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationMapValueExtractor.java index a0b5970..08bc2a1 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationMapValueExtractor.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationMapValueExtractor.java @@ -49,7 +49,6 @@ import org.apache.commons.configuration.Configuration; import br.gov.frameworkdemoiselle.annotation.Priority; import br.gov.frameworkdemoiselle.configuration.ConfigurationValueExtractor; -import br.gov.frameworkdemoiselle.util.Strings; @Priority(EXTENSIONS_L1_PRIORITY) public class ConfigurationMapValueExtractor implements ConfigurationValueExtractor { diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationPrimitiveOrWrapperValueExtractor.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationPrimitiveOrWrapperValueExtractor.java index f63cfae..b22799e 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationPrimitiveOrWrapperValueExtractor.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationPrimitiveOrWrapperValueExtractor.java @@ -49,7 +49,6 @@ import org.apache.commons.lang.ClassUtils; import br.gov.frameworkdemoiselle.annotation.Priority; import br.gov.frameworkdemoiselle.configuration.ConfigurationValueExtractor; -import br.gov.frameworkdemoiselle.util.Strings; @Priority(EXTENSIONS_L1_PRIORITY) public class ConfigurationPrimitiveOrWrapperValueExtractor implements ConfigurationValueExtractor { diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationStringValueExtractor.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationStringValueExtractor.java index 2d5def9..9166f3e 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationStringValueExtractor.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/internal/implementation/ConfigurationStringValueExtractor.java @@ -44,7 +44,6 @@ import org.apache.commons.configuration.Configuration; import br.gov.frameworkdemoiselle.annotation.Priority; import br.gov.frameworkdemoiselle.configuration.ConfigurationValueExtractor; -import br.gov.frameworkdemoiselle.util.Strings; @Priority(EXTENSIONS_L1_PRIORITY) public class ConfigurationStringValueExtractor implements ConfigurationValueExtractor { diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Managed.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Managed.java new file mode 100644 index 0000000..7af7cbb --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Managed.java @@ -0,0 +1,80 @@ +/* + * Demoiselle Framework + * Copyright (C) 2011 SERPRO + * ---------------------------------------------------------------------------- + * This file is part of Demoiselle Framework. + * + * Demoiselle Framework is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License version 3 + * as published by the Free Software Foundation. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this program; if not, see + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Fifth Floor, Boston, MA 02110-1301, USA. + * ---------------------------------------------------------------------------- + * Este arquivo é parte do Framework Demoiselle. + * + * O Framework Demoiselle é um software livre; você pode redistribuí-lo e/ou + * modificá-lo dentro dos termos da GNU LGPL versão 3 como publicada pela Fundação + * do Software Livre (FSF). + * + * Este programa é distribuído na esperança que possa ser útil, mas SEM NENHUMA + * GARANTIA; sem uma garantia implícita de ADEQUAÇÃO a qualquer MERCADO ou + * APLICAÇÃO EM PARTICULAR. Veja a Licença Pública Geral GNU/LGPL em português + * para maiores detalhes. + * + * Você deve ter recebido uma cópia da GNU LGPL versão 3, sob o título + * "LICENCA.txt", junto com esse programa. Se não, acesse + * ou escreva para a Fundação do Software Livre (FSF) Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02111-1301, USA. + */ +package br.gov.frameworkdemoiselle.management.annotation; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Stereotype; +import javax.enterprise.util.Nonbinding; +import javax.interceptor.InterceptorBinding; + +/** + * Stereotype to indicate a given class is managed. What it means is that an external client can manage the application + * this class is in by reading or writing it's attributes and calling it's operations. + *

+ * Only fields annotated with {@link br.gov.frameworkdemoiselle.management.annotation.Property} or + * methods annotated with {@link br.gov.frameworkdemoiselle.management.annotation.Operation} will be exposed + * to clients.

+ *

This stereotype only defines a class as managed, you need to choose an extension that will expose this managed class + * to external clients using any technology available. One example is the Demoiselle JMX extension, that will expose + * managed classes as MBeans.

+ * + * @author SERPRO + */ +@Documented +@Stereotype +@InterceptorBinding +@Inherited +@Retention(RUNTIME) +@Target({ TYPE }) +@ApplicationScoped +public @interface Managed { + + /** + * @return Human readable description of this managed class. + */ + @Nonbinding + String description() default ""; + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Operation.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Operation.java new file mode 100644 index 0000000..7a540af --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Operation.java @@ -0,0 +1,37 @@ +package br.gov.frameworkdemoiselle.management.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.util.Nonbinding; +import javax.management.MBeanException; + +/** + *

Indicates that a method is an operation, meaning you can manage some aspect of the application by calling it.

+ *

This annotation can't be used together with {@link Property}, doing so will throw a {@link MBeanException}.

+ * + * @author SERPRO + * + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Operation { + + /** + * Description that will be used to publish the operation to clients. + * Defaults to an empty description. + */ + @Nonbinding + String description() default ""; + + /** + * Type of operation. Defaults to {@link OperationType#UNKNOWN}. + */ + @Nonbinding + OperationType type() default OperationType.UNKNOWN; + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/OperationParameter.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/OperationParameter.java new file mode 100644 index 0000000..2046ffa --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/OperationParameter.java @@ -0,0 +1,37 @@ +package br.gov.frameworkdemoiselle.management.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.util.Nonbinding; + +/** + *

Optional annotation to write additional detail about an operation's parameter.

+ *

This annotation is ignored for non-operation methods.

+ * + * @author SERPRO + * + */ +@Documented +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OperationParameter { + + /** + * Name that will be used to publish this operation's parameter to clients. + */ + @Nonbinding + String name(); + + /** + * Optional description that will be used to publish this operation's parameter to clients. + * Defaults to an empty description. + */ + @Nonbinding + String description() default ""; + + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/OperationType.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/OperationType.java new file mode 100644 index 0000000..4a0eef9 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/OperationType.java @@ -0,0 +1,45 @@ +package br.gov.frameworkdemoiselle.management.annotation; + +import javax.management.MBeanOperationInfo; + + +/** + * Define the operation type for an operation inside a Managed class. + * + * + * @author SERPRO + * + */ +public enum OperationType { + + /** + * Operation is write-only + */ + ACTION(MBeanOperationInfo.ACTION) + , + /** + * Operation is read-only + */ + INFO(MBeanOperationInfo.INFO) + , + /** + * Operation is read-write + */ + ACTION_INFO(MBeanOperationInfo.ACTION_INFO) + , + /** + * Operation is unkown + */ + UNKNOWN(MBeanOperationInfo.UNKNOWN); + + private int operationTypeValue; + + private OperationType(int type){ + this.operationTypeValue = type; + } + + public int getValue(){ + return operationTypeValue; + } + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Property.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Property.java new file mode 100644 index 0000000..17ebf12 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/Property.java @@ -0,0 +1,33 @@ +package br.gov.frameworkdemoiselle.management.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.util.Nonbinding; + +/** + *

Indicates that a field must be exposed as a property to management clients.

+ *

The property will be writable if there's a public setter method + * declared for the field and readable if there's a getter method.

+ *

It's a runtime error to annotate a field with no getter and no setter method.

+ *

It's also a runtime error to declare a field as a property and one or both of it's getter and setter + * methods as an operation using the {@link Operation} annotation.

+ * + * @author SERPRO + * + */ +@Documented +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Property { + + /** + * @return The description of this property exposed to management clients. + */ + @Nonbinding + String description() default ""; + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/validation/AllowedValues.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/validation/AllowedValues.java new file mode 100644 index 0000000..2afa287 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/annotation/validation/AllowedValues.java @@ -0,0 +1,40 @@ +package br.gov.frameworkdemoiselle.management.annotation.validation; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.validation.Constraint; + +import br.gov.frameworkdemoiselle.management.internal.validators.AllowedValuesValidator; + +@Documented +@Target({ FIELD}) +@Retention(RUNTIME) +@Constraint(validatedBy = AllowedValuesValidator.class) +/** + * Validate a value against a list of allowed values. + * + * @author serpro + * + */ +public @interface AllowedValues { + + /** + * List of accepted values + */ + String[] allows(); + + /** + * Type of allowed values. Defaults to {@link ValueType#STRING}. + */ + ValueType valueType() default ValueType.STRING; + + enum ValueType { + STRING,INTEGER,DECIMAL; + } + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/extension/ManagementExtension.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/extension/ManagementExtension.java new file mode 100644 index 0000000..69184d5 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/extension/ManagementExtension.java @@ -0,0 +1,20 @@ +package br.gov.frameworkdemoiselle.management.extension; + +import java.util.List; + +import br.gov.frameworkdemoiselle.management.internal.ManagedType; + +/** + * + * Define an entry point for monitoring extension. + * + * @author serpro + * + */ +public interface ManagementExtension { + + void initialize(List managedTypes); + + void shutdown(List managedTypes); + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/ManagedType.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/ManagedType.java new file mode 100644 index 0000000..2f8ca16 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/ManagedType.java @@ -0,0 +1,295 @@ +package br.gov.frameworkdemoiselle.management.internal; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.TreeMap; + +import br.gov.frameworkdemoiselle.DemoiselleException; +import br.gov.frameworkdemoiselle.internal.producer.ResourceBundleProducer; +import br.gov.frameworkdemoiselle.management.annotation.Managed; +import br.gov.frameworkdemoiselle.management.annotation.Operation; +import br.gov.frameworkdemoiselle.management.annotation.OperationParameter; +import br.gov.frameworkdemoiselle.management.annotation.Property; +import br.gov.frameworkdemoiselle.util.ResourceBundle; + +/** + * Represents a detected managed type, a Java class annotated with {@link Managed}. + * + * @author serpro + */ +public class ManagedType { + + private Class type; + + private TreeMap fields; + + private TreeMap operationMethods; + + private ResourceBundle bundle; + + private String description; + + public ManagedType(Class type) { + bundle = ResourceBundleProducer.create("demoiselle-core-bundle"); + + if (type == null) { + throw new DemoiselleException(bundle.getString("management-null-class-defined")); + } + if (!type.isAnnotationPresent(Managed.class)) { + throw new DemoiselleException(bundle.getString("management-no-annotation-found", type.getCanonicalName())); + } + + this.type = type; + fields = new TreeMap(); + operationMethods = new TreeMap(); + this.description = type.getAnnotation(Managed.class).description(); + + initialize(); + } + + public Class getType() { + return type; + } + + public String getDescription() { + return description; + } + + public TreeMap getFields() { + return fields; + } + + public TreeMap getOperationMethods() { + return operationMethods; + } + + private void initialize() { + // Para cada atributo verifica se ele está anotado com Property e extrai as informações dele (método get, set e + // descrição do atributo). + Field[] fields = type.getDeclaredFields(); + if (fields != null) { + for (Field field : fields) { + if (field.isAnnotationPresent(Property.class)) { + // Obtém os métodos GET e SET para esta propriedade + Method getterMethod = getGetterMethod(field); + Method setterMethod = getSetterMethod(field); + if (getterMethod == null && setterMethod == null) { + throw new DemoiselleException(bundle.getString("management-invalid-property-no-getter-setter", + type.getSimpleName(), field.getName())); + } else if ((getterMethod != null && getterMethod.isAnnotationPresent(Operation.class)) + || (setterMethod != null && setterMethod.isAnnotationPresent(Operation.class))) { + throw new DemoiselleException(bundle.getString("management-invalid-property-as-operation", + type.getSimpleName())); + } + + String propertyDescription = field.getAnnotation(Property.class).description(); + + this.fields.put(field.getName(), new FieldDetail(field, propertyDescription, getterMethod, + setterMethod)); + } + } + } + + // Para cada metodo verifica se ele está anotado com Operation e cria um MBeanOperationInfo para ele. + Method[] methodList = type.getMethods(); + if (methodList != null) { + for (Method method : methodList) { + Operation opAnnotation = method.getAnnotation(Operation.class); + + if (opAnnotation != null) { + // Lemos as informações sobre o método e criamos uma instância + // de MethodDetail para representar este método como uma + // operação. + + Class[] parameterTypes = method.getParameterTypes(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + ParameterDetail[] parameterDetails = new ParameterDetail[parameterTypes.length]; + + for (int i = 0; i < parameterTypes.length; i++) { + OperationParameter paramAnnotation = null; + for (Annotation annotation : parameterAnnotations[i]) { + if (annotation.annotationType() == OperationParameter.class) { + paramAnnotation = (OperationParameter) annotation; + break; + } + } + + String name = paramAnnotation != null ? paramAnnotation.name() : ("arg" + i); + String description = paramAnnotation != null ? paramAnnotation.description() : null; + + parameterDetails[i] = new ParameterDetail(parameterTypes[i], name, description); + } + + // Com todas as informações, criamos nossa instância de MethodDetail e + // acrescentamos na lista de todas as operações. + MethodDetail detail = new MethodDetail(method, opAnnotation.description(), parameterDetails); + operationMethods.put(method.getName(), detail); + } + } + } + } + + /** + * Returns the public getter method for a given field, or null if no getter method can be found. + */ + private Method getGetterMethod(Field field) { + StringBuffer getterMethodName = new StringBuffer() + .append("get") + .append(field.getName().substring(0, 1).toUpperCase()) + .append(field.getName().substring(1)); + + Method getterMethod; + + try { + getterMethod = type.getMethod(getterMethodName.toString()); + } catch (Exception e) { + getterMethod = null; + } + + // Se atributo for boolean, procura método getter no formato "isAttribute". + if (getterMethod == null + && (Boolean.TYPE.isAssignableFrom(field.getType()) || Boolean.class.isAssignableFrom(field.getType()))) { + // Boolean.TYPE representa o tipo primitivo "boolean", Boolean.class é a classe wrapper. + getterMethodName = new StringBuffer() + .append("is") + .append(field.getName().substring(0, 1).toUpperCase()) + .append(field.getName().substring(1).toUpperCase()); + + try { + getterMethod = type.getMethod(getterMethodName.toString()); + } catch (Exception e) { + getterMethod = null; + } + } + + return getterMethod; + } + + /** + * Returns the public setter method for a given field, or null if no setter method can be found. + */ + private Method getSetterMethod(Field field) { + StringBuffer setterMethodName = new StringBuffer() + .append("set") + .append(field.getName().substring(0, 1).toUpperCase()) + .append(field.getName().substring(1)); + + Method setterMethod; + + try { + setterMethod = type.getMethod(setterMethodName.toString() , field.getType()); + } catch (Exception e) { + setterMethod = null; + } + + return setterMethod; + } + + public final class FieldDetail { + + private final Field field; + + private final String description; + + private Method getterMethod; + + private Method setterMethod; + + public FieldDetail(Field field, String description, Method getterMethod, Method setterMethod) { + super(); + this.field = field; + this.description = description; + this.getterMethod = getterMethod; + this.setterMethod = setterMethod; + } + + public Field getField() { + return field; + } + + public String getDescription() { + return description; + } + + public Method getGetterMethod() { + return getterMethod; + } + + public Method getSetterMethod() { + return setterMethod; + } + + } + + public final class MethodDetail { + + private final Method method; + + private final ParameterDetail[] parameterTypers; + + private String description; + + public MethodDetail(Method method, String description, ParameterDetail[] parameterTypers) { + super(); + this.method = method; + this.description = description; + this.parameterTypers = parameterTypers; + } + + public Method getMethod() { + return method; + } + + public ParameterDetail[] getParameterTypers() { + return parameterTypers; + } + + public String getDescription() { + return description; + } + + } + + public final class ParameterDetail { + + private final Class parameterType; + + private final String parameterName; + + private final String parameterDescription; + + public ParameterDetail(Class parameterType, String parameterName, String parameterDescription) { + super(); + this.parameterType = parameterType; + this.parameterName = parameterName; + this.parameterDescription = parameterDescription; + } + + public Class getParameterType() { + return parameterType; + } + + public String getParameterName() { + return parameterName; + } + + public String getParameterDescription() { + return parameterDescription; + } + } + + /** + * Indicates another {@link ManagedType} represents the same {@link Class} as this one. This method also supports a + * {@link Class} as a parameter, in this case it will return true if the passed class is exactly the + * same Java class represented by this {@link ManagedType}. + */ + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + + return ((ManagedType) other).getType().getCanonicalName().equals(this.getType().getCanonicalName()); + } +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/MonitoringManager.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/MonitoringManager.java new file mode 100644 index 0000000..ab1c034 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/MonitoringManager.java @@ -0,0 +1,254 @@ +package br.gov.frameworkdemoiselle.management.internal; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.management.ReflectionException; + +import org.slf4j.Logger; + +import br.gov.frameworkdemoiselle.DemoiselleException; +import br.gov.frameworkdemoiselle.internal.context.ContextManager; +import br.gov.frameworkdemoiselle.internal.context.ManagedContext; +import br.gov.frameworkdemoiselle.management.annotation.Managed; +import br.gov.frameworkdemoiselle.management.annotation.Property; +import br.gov.frameworkdemoiselle.management.extension.ManagementExtension; +import br.gov.frameworkdemoiselle.management.internal.ManagedType.MethodDetail; +import br.gov.frameworkdemoiselle.management.notification.AttributeChangeNotification; +import br.gov.frameworkdemoiselle.management.notification.NotificationManager; +import br.gov.frameworkdemoiselle.util.Beans; +import br.gov.frameworkdemoiselle.util.ResourceBundle; + +/** + * A manager that helps implementators of the management framework to obtain a + * list of managed classes, set or obtain values from them or invoke operations + * over them. + * + * @author serpro + */ +@ApplicationScoped +public class MonitoringManager { + + @Inject + private Logger logger; + + @Inject + private ResourceBundle bundle; + + private final List managedTypes = new ArrayList(); + + public void addManagedType(ManagedType managedType) { + managedTypes.add(managedType); + } + + /** + * @return A list all managed types, classes annotated with {@link Managed}. + * The returned list is a shallow copy of the internal list, so you + * are free to modify it. + * + * TODO precisamos desse clone na lista? + */ + public List getManagedTypes() { + ArrayList cloneList = new ArrayList(); + cloneList.addAll(managedTypes); + return cloneList; + } + + /** + * Invoke an operation over a managed type. + * + * @param managedType + * A type annotated with {@link Managed}. This method will create + * an (or obtain an already created) instance of this type and + * invoke the operation over it. + * @param actionName + * Name of method to be invoked, the type must have this + * operation on it's list + * @param params + * List of values for the operation parameters. Can be + * null if the operation require no parameters. + * @return The return value of the original invoked operation. Methods of + * return type void will return the {@link Void} type. + * @throws ReflectionException + * In case the operation doesn't exist or have a different + * signature + */ + public synchronized Object invoke(ManagedType managedType, String actionName, + Object[] params) { + if ( managedTypes.contains(managedType) ) { + activateContexts(managedType.getType()); + + Object delegate = Beans.getReference(managedType.getType()); + + MethodDetail method = managedType.getOperationMethods().get(actionName); + + if (method != null) { + try { + logger.debug(bundle + .getString("management-debug-invoking-operation",actionName,managedType.getType().getCanonicalName())); + return method.getMethod().invoke(delegate, params); + } catch (Exception e) { + throw new DemoiselleException(bundle.getString( + "management-invoke-error", actionName), e); + } finally { + deactivateContexts(managedType.getType()); + } + } else { + throw new DemoiselleException(bundle.getString( + "management-invoke-error", actionName)); + } + } else { + throw new DemoiselleException( + bundle.getString("management-type-not-found")); + } + } + + /** + * Retrieve the current value of a property from a managed type. Properties + * are attributes annotated with {@link Property}. + * + * @param managedType The type that has the property the client wants to know the value of. + * @param propertyName The name of the property + * @return The current value of the property + */ + public synchronized Object getProperty(ManagedType managedType, String propertyName) { + + if ( managedTypes.contains(managedType) ) { + Method getterMethod = managedType.getFields().get(propertyName).getGetterMethod(); + + if (getterMethod != null) { + logger.debug(bundle.getString( + "management-debug-acessing-property", getterMethod + .getName(), managedType.getType().getCanonicalName())); + + activateContexts(managedType.getType()); + + try { + Object delegate = Beans.getReference(managedType.getType()); + + return getterMethod.invoke(delegate, (Object[]) null); + } catch (Exception e) { + throw new DemoiselleException(bundle.getString( + "management-invoke-error", getterMethod.getName()), + e); + } finally { + deactivateContexts(managedType.getType()); + } + } else { + throw new DemoiselleException(bundle.getString( + "management-invoke-error", propertyName)); + } + } else { + throw new DemoiselleException( + bundle.getString("management-type-not-found")); + } + } + + /** + * Sets a new value for a property contained inside a managed type. A property + * is an attribute annotated with {@link Property}. + * + * @param managedType The type that has access to the property + * @param propertyName The name of the property + * @param newValue The new value of the property + */ + public synchronized void setProperty(ManagedType managedType, String propertyName, + Object newValue) { + + if ( managedTypes.contains(managedType) ) { + // Procura o método set do atributo em questão + Method method = managedType.getFields().get(propertyName).getSetterMethod(); + if (method != null) { + logger.debug(bundle.getString( + "management-debug-setting-property", method.getName(), + managedType.getType().getCanonicalName())); + + // Obtém uma instância da classe gerenciada, lembrando que + // classes + // anotadas com @Managed são sempre singletons. + activateContexts(managedType.getType()); + try { + Object delegate = Beans.getReference(managedType.getType()); + + Method getterMethod = managedType.getFields().get(propertyName).getGetterMethod(); + Object oldValue; + try{ + oldValue = getterMethod.invoke(delegate, (Object[])null); + } + catch(Exception e){ + oldValue = null; + } + + method.invoke(delegate, new Object[] { newValue }); + + //Manda uma notificação de mudança de atributo + NotificationManager notificationManager = Beans.getReference(NotificationManager.class); + Class attributeType = newValue!=null ? newValue.getClass() : null; + AttributeChangeNotification notification = new AttributeChangeNotification(bundle.getString(""), propertyName, attributeType, oldValue, newValue); + notificationManager.sendAttributeChangedMessage(notification); + + } catch (Exception e) { + throw new DemoiselleException(bundle.getString( + "management-invoke-error", method.getName()), e); + } finally { + deactivateContexts(managedType.getType()); + } + + } else { + throw new DemoiselleException(bundle.getString( + "management-invoke-error", propertyName)); + } + } else { + throw new DemoiselleException( + bundle.getString("management-type-not-found")); + } + + } + + private void activateContexts(Class managedType) { + logger.debug(bundle.getString("management-debug-starting-custom-context", + ManagedContext.class.getCanonicalName(), + managedType.getCanonicalName())); + + ContextManager.activate(ManagedContext.class,RequestScoped.class); + } + + private void deactivateContexts(Class managedType) { + logger.debug(bundle.getString("management-debug-stoping-custom-context", + ManagedContext.class.getCanonicalName(), + managedType.getCanonicalName())); + + ContextManager.deactivate(ManagedContext.class,RequestScoped.class); + } + + public void shutdown(Collection> monitoringExtensions) { + + for (Class monitoringExtensionClass : monitoringExtensions) { + + ManagementExtension monitoringExtension = Beans.getReference(monitoringExtensionClass); + + monitoringExtension.shutdown(this.getManagedTypes()); + + } + + } + + public void initialize(Collection> monitoringExtensions) { + + for (Class monitoringExtensionClass : monitoringExtensions) { + + ManagementExtension monitoringExtension = Beans + .getReference(monitoringExtensionClass); + + monitoringExtension.initialize(this.getManagedTypes()); + + } + + } + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/event/NotificationEvent.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/event/NotificationEvent.java new file mode 100644 index 0000000..1dfdab7 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/event/NotificationEvent.java @@ -0,0 +1,29 @@ +package br.gov.frameworkdemoiselle.management.internal.notification.event; + +import br.gov.frameworkdemoiselle.management.notification.Notification; +import br.gov.frameworkdemoiselle.management.notification.NotificationManager; + +/** + * Event fired when a notification is sent by {@link NotificationManager}. + * Implementators can capture this event and by notified when the {@link NotificationManager} + * sends notifications, so they can pass the notification to a underlying protocol such as JMX. + * + * @author serpro + * + */ +public class NotificationEvent { + + private Notification notification; + + public NotificationEvent(Notification notification){ + this.notification = notification; + } + + public Notification getNotification() { + return notification; + } + + public void setNotification(Notification notification) { + this.notification = notification; + } +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/qualifier/AttributeChange.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/qualifier/AttributeChange.java new file mode 100644 index 0000000..5c1a394 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/qualifier/AttributeChange.java @@ -0,0 +1,26 @@ +package br.gov.frameworkdemoiselle.management.internal.notification.qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +import br.gov.frameworkdemoiselle.management.internal.notification.event.NotificationEvent; +import br.gov.frameworkdemoiselle.management.notification.AttributeChangeNotification; + +/** + * + * Enables {@link NotificationEvent} observers to trigger only with notifications + * of the specialized type {@link AttributeChangeNotification}. + * + * @author serpro + * + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) +public @interface AttributeChange { + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/qualifier/Generic.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/qualifier/Generic.java new file mode 100644 index 0000000..14732ee --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/notification/qualifier/Generic.java @@ -0,0 +1,26 @@ +package br.gov.frameworkdemoiselle.management.internal.notification.qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +import br.gov.frameworkdemoiselle.management.internal.notification.event.NotificationEvent; +import br.gov.frameworkdemoiselle.management.notification.Notification; + +/** + * + * Enables {@link NotificationEvent} observers to trigger only with notifications + * of the base type {@link Notification}. + * + * @author serpro + * + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE}) +public @interface Generic { + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/validators/AllowedValuesValidator.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/validators/AllowedValuesValidator.java new file mode 100644 index 0000000..3dd96c1 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/internal/validators/AllowedValuesValidator.java @@ -0,0 +1,68 @@ +package br.gov.frameworkdemoiselle.management.internal.validators; + +import java.math.BigDecimal; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +import br.gov.frameworkdemoiselle.management.annotation.validation.AllowedValues; + + +public class AllowedValuesValidator implements ConstraintValidator { + + private br.gov.frameworkdemoiselle.management.annotation.validation.AllowedValues.ValueType valueType; + private String[] allowedValues; + + @Override + public void initialize(AllowedValues constraintAnnotation) { + valueType = constraintAnnotation.valueType(); + allowedValues = constraintAnnotation.allows(); + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + + if (value==null){ + return false; + } + + switch(valueType){ + case STRING: + for (String str : allowedValues){ + if (str.equals(value)) return true; + } + return false; + + case INTEGER: + try{ + Integer number = Integer.valueOf(value.toString()); + String strNumber = number.toString(); + for (String str : allowedValues){ + if (str.equals(strNumber)) return true; + } + + return false; + } + catch(NumberFormatException ne){ + return false; + } + + case DECIMAL: + try{ + BigDecimal number = new BigDecimal(value.toString()); + String strNumber = number.toString(); + for (String str : allowedValues){ + if (str.equals(strNumber)) return true; + } + } + catch(NumberFormatException ne){ + return false; + } + + return false; + } + + return false; + } + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/AttributeChangeNotification.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/AttributeChangeNotification.java new file mode 100644 index 0000000..b8012f8 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/AttributeChangeNotification.java @@ -0,0 +1,74 @@ +package br.gov.frameworkdemoiselle.management.notification; + +/** + * Special notification to denote an attribute has changed values. + * + * @see Notification + * + * @author serpro + * + */ +public class AttributeChangeNotification extends Notification { + + private String attributeName; + + private Class attributeType; + + private Object oldValue; + + private Object newValue; + + public AttributeChangeNotification(){} + + public AttributeChangeNotification(Object message, String attributeName, Class attributeType, Object oldValue, + Object newValue) { + super(message); + this.attributeName = attributeName; + this.attributeType = attributeType; + this.oldValue = oldValue; + this.newValue = newValue; + } + + + public String getAttributeName() { + return attributeName; + } + + + public void setAttributeName(String attributeName) { + this.attributeName = attributeName; + } + + + public Class getAttributeType() { + return attributeType; + } + + + public void setAttributeType(Class attributeType) { + this.attributeType = attributeType; + } + + + public Object getOldValue() { + return oldValue; + } + + + public void setOldValue(Object oldValue) { + this.oldValue = oldValue; + } + + + public Object getNewValue() { + return newValue; + } + + + public void setNewValue(Object newValue) { + this.newValue = newValue; + } + + + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/Notification.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/Notification.java new file mode 100644 index 0000000..7789f28 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/Notification.java @@ -0,0 +1,41 @@ +package br.gov.frameworkdemoiselle.management.notification; + +/** + * + * Notification that can be sent by the {@link NotificationManager}. + * + * @author serpro + * + */ +public class Notification { + + private Object message; + + public Notification(){ + } + + public Notification(Object message) { + super(); + this.message = message; + } + + + public Object getMessage() { + return message; + } + + + public void setMessage(Object message) { + this.message = message; + } + + + public Class getType() { + if (message!=null){ + return message.getClass(); + } + + return null; + } + +} diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/NotificationManager.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/NotificationManager.java new file mode 100644 index 0000000..f0b2a70 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/management/notification/NotificationManager.java @@ -0,0 +1,63 @@ +package br.gov.frameworkdemoiselle.management.notification; + +import java.io.Serializable; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Event; +import javax.enterprise.event.Observes; +import javax.inject.Inject; + +import br.gov.frameworkdemoiselle.management.internal.notification.event.NotificationEvent; +import br.gov.frameworkdemoiselle.management.internal.notification.qualifier.AttributeChange; +import br.gov.frameworkdemoiselle.management.internal.notification.qualifier.Generic; +import br.gov.frameworkdemoiselle.util.Beans; + +/** + * + *

Central class to manage sending notifications to management clients. + * This class allows applications to send management notifications without + * knowledge of the technology used to send those notifications.

+ * + *

To obtain an instance of the {@link NotificationManager} simply inject it in + * your code using {@link Inject} or the {@link Beans#getReference(Class beanType)} method. The {@link NotificationManager} + * is {@link ApplicationScoped}, so you can inject it as many times as needed and still have only one instance per application.

+ * + *

Implementators of management protocols must observe the {@link NotificationEvent} event (using the {@link Observes} annotation), this way + * they will receive an event containing the original notification and can translate this notification to a specific protocol. Optionaly, + * the implementator can use qualifiers like the {@link Generic} and {@link AttributeChange} qualifiers + * to filter what king of notifications they will handle. One example of an implementator is the demoiselle-jmx extension.

+ * + * @author serpro + * + */ +@ApplicationScoped +@SuppressWarnings("serial") +public class NotificationManager implements Serializable{ + + @Inject + @Generic + private Event genericNotificationEvent; + + @Inject + @AttributeChange + private Event attributeChangeNotificationEvent; + + /** + * Sends a generic notification to all management clients. + * + * @param notification The notification to send + */ + public void sendNotification(Notification notification) { + genericNotificationEvent.fire(new NotificationEvent(notification)); + } + + /** + * Sends a notification comunicating about a change of value for an attribute. + * + * @param notification Special notification communicating a change of value in an attribute. + * + */ + public void sendAttributeChangedMessage(AttributeChangeNotification notification){ + attributeChangeNotificationEvent.fire(new NotificationEvent(notification)); + } +} diff --git a/impl/core/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/impl/core/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension index a696db3..7106f5a 100644 --- a/impl/core/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension +++ b/impl/core/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -3,5 +3,6 @@ br.gov.frameworkdemoiselle.internal.bootstrap.ConfigurationBootstrap br.gov.frameworkdemoiselle.internal.bootstrap.TransactionBootstrap br.gov.frameworkdemoiselle.internal.bootstrap.AuthenticatorBootstrap br.gov.frameworkdemoiselle.internal.bootstrap.AuthorizerBootstrap +br.gov.frameworkdemoiselle.internal.bootstrap.ManagementBootstrap br.gov.frameworkdemoiselle.internal.bootstrap.StartupBootstrap br.gov.frameworkdemoiselle.internal.bootstrap.ShutdownBootstrap diff --git a/impl/core/src/main/resources/demoiselle-core-bundle.properties b/impl/core/src/main/resources/demoiselle-core-bundle.properties index 20b711c..b50c510 100644 --- a/impl/core/src/main/resources/demoiselle-core-bundle.properties +++ b/impl/core/src/main/resources/demoiselle-core-bundle.properties @@ -96,4 +96,17 @@ does-not-have-role=Usu\u00E1rio {0} n\u00E3o possui a(s) role(s)\: {1} does-not-have-role-ui=Para acessar este recurso \u00E9 necess\u00E1rio ser {0} user-has-role=Usu\u00E1rio {0} possui a(s) role(s)\: {1} -authenticator-not-defined=Nenhum mecanismo de autentica\u00E7\u00E3o foi definido. Para utilizar {0} \u00E9 preciso definir a propriedade frameworkdemoiselle.security.authenticator.class como mecanismo de autentica\u00E7\u00E3o desejado no arquivo demoiselle.properties. \ No newline at end of file +authenticator-not-defined=Nenhum mecanismo de autentica\u00E7\u00E3o foi definido. Para utilizar {0} \u00E9 preciso definir a propriedade frameworkdemoiselle.security.authenticator.class como mecanismo de autentica\u00E7\u00E3o desejado no arquivo demoiselle.properties. + +management-null-class-defined=A classe gerenciada informada n\u00E3o pode ser nula. +management-no-annotation-found=Classe {0} precisa ser anotada com @Managed. +management-invalid-property-no-getter-setter=Falha ao inicializar classe gerenciada {0}, n\u00E3o foi encontrado um m\u00E9todo get ou m\u00E9todo set para a propriedade {1}. +management-invalid-property-as-operation=Falha ao inicializar classe gerenciada {0}, n\u00E3o \u00E9 poss\u00EDvel declarar uma propriedade cujo m\u00E9todo get ou set \u00E9 uma opera\u00E7\u00E3o. +management-introspection-error=Erro ao ler atributos da classe gerenciada {0}. +management-type-not-found=A classe gerenciada informada n\u00E3o existe: {0}. +management-invoke-error=Erro ao tentar invocar a opera\u00E7\u00E3o "{0}" da classe gerenciada, a opera\u00E7\u00E3o n\u00E3o foi encontrada. +management-debug-acessing-property=Acessando propriedade {0} da classe gerenciada {1}. +management-debug-setting-property=Definindo novo valor para propriedade {0} da classe gerenciada {1}. +management-debug-invoking-operation=Invocando opera\u00E7\u00E3o {0} da classe gerenciada {1}. +management-debug-starting-custom-context=Levantando contexto {0} para executar comando na classe gerenciada {1}. +management-debug-stoping-custom-context=Desligando contexto {0} para classe gerenciada {1}. diff --git a/impl/core/src/test/java/management/DummyManagedClass.java b/impl/core/src/test/java/management/DummyManagedClass.java new file mode 100644 index 0000000..5511456 --- /dev/null +++ b/impl/core/src/test/java/management/DummyManagedClass.java @@ -0,0 +1,74 @@ +package management; + +import java.util.UUID; + +import br.gov.frameworkdemoiselle.management.annotation.Managed; +import br.gov.frameworkdemoiselle.management.annotation.Operation; +import br.gov.frameworkdemoiselle.management.annotation.Property; +import br.gov.frameworkdemoiselle.management.annotation.validation.AllowedValues; +import br.gov.frameworkdemoiselle.management.annotation.validation.AllowedValues.ValueType; + +@Managed +public class DummyManagedClass { + + @Property + @AllowedValues(allows={"f","m","F","M"},valueType=ValueType.INTEGER) + private Integer id; + + @Property + private String uuid; + + @Property + private String writeOnlyProperty; + + /** + * Propriedade para testar detecção de métodos GET e SET quando propriedade tem apenas uma letra. + */ + @Property + private Integer a; + + /** + * Propriedade para testar detecção de métodos GET e SET quando propriedade tem apenas letras maiúsculas. + */ + @Property + private Integer MAIUSCULO; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setWriteOnlyProperty(String newValue){ + this.writeOnlyProperty = newValue; + } + + public Integer getA() { + return a; + } + + public void setA(Integer a) { + this.a = a; + } + + public Integer getMAIUSCULO() { + return MAIUSCULO; + } + + + public void setMAIUSCULO(Integer mAIUSCULO) { + MAIUSCULO = mAIUSCULO; + } + + @Operation(description="Generates a random UUID") + public String generateUUID(){ + this.uuid = UUID.randomUUID().toString(); + return this.uuid; + } +} diff --git a/impl/core/src/test/java/management/DummyManagementExtension.java b/impl/core/src/test/java/management/DummyManagementExtension.java new file mode 100644 index 0000000..ac48b12 --- /dev/null +++ b/impl/core/src/test/java/management/DummyManagementExtension.java @@ -0,0 +1,29 @@ +package management; + +import java.util.List; + +import javax.inject.Inject; + +import br.gov.frameworkdemoiselle.management.extension.ManagementExtension; +import br.gov.frameworkdemoiselle.management.internal.ManagedType; + +public class DummyManagementExtension implements ManagementExtension { + + @Inject + private ManagedClassStore store; + + @Override + public void initialize(List managedTypes) { + // Armazena os beans managed detectados neste store, + // para depois serem testados. + store.setManagedTypes(managedTypes); + } + + @Override + public void shutdown(List managedTypes) { + // Limpa o store, depois o teste verificará se + // o processo de shutdown rodou e limpou o store. + store.setManagedTypes(null); + } + +} diff --git a/impl/core/src/test/java/management/ManagedClassStore.java b/impl/core/src/test/java/management/ManagedClassStore.java new file mode 100644 index 0000000..73f2acd --- /dev/null +++ b/impl/core/src/test/java/management/ManagedClassStore.java @@ -0,0 +1,23 @@ +package management; + +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; + +import br.gov.frameworkdemoiselle.management.internal.ManagedType; + +@ApplicationScoped +public class ManagedClassStore { + + private List managedTypes = null; + + + public List getManagedTypes() { + return managedTypes; + } + + public void setManagedTypes(List managedTypes) { + this.managedTypes = managedTypes; + } + +} diff --git a/impl/core/src/test/java/management/ManagementBootstrapTest.java b/impl/core/src/test/java/management/ManagementBootstrapTest.java new file mode 100644 index 0000000..ec1d97e --- /dev/null +++ b/impl/core/src/test/java/management/ManagementBootstrapTest.java @@ -0,0 +1,68 @@ +package management; + +import javax.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import test.Tests; +import br.gov.frameworkdemoiselle.lifecycle.AfterShutdownProccess; +import br.gov.frameworkdemoiselle.management.extension.ManagementExtension; +import br.gov.frameworkdemoiselle.util.Beans; + + +@RunWith(Arquillian.class) +public class ManagementBootstrapTest { + + @Inject + private ManagedClassStore store; + + @Deployment + public static JavaArchive createDeployment() { + JavaArchive deployment = Tests.createDeployment(ManagementBootstrapTest.class); + + /*deployment + .addClass(ManagedClassStore.class) + .addClass(DummyManagedClass.class) + .addClass(DummyManagementExtension.class);*/ + + return deployment; + } + + /** + * Test if a a management extension (a library that implements {@link ManagementExtension}) is correctly detected. + */ + @Test + public void testManagementExtensionRegistration(){ + + //"store" é application scoped e é usado pelo DummyManagementExtension para + //armazenar todos os beans anotados com @Managed. Se o bootstrap rodou corretamente, + //ele chamou DummyManagementExtension.initialize e este store conterá o bean de teste que anotamos. + Assert.assertNotNull(store.getManagedTypes()); + Assert.assertEquals(1, store.getManagedTypes().size()); + + } + + /** + * Test if a a management extension's (a library that implements {@link ManagementExtension}) shutdown + * method is correctly called upon application shutdown. + */ + @Test + public void testManagementExtensionShutdown(){ + + //"store" é application scoped e é usado pelo DummyManagementExtension para + //armazenar todos os beans anotados com @Managed. Se o bootstrap rodou corretamente, + //ele chamou DummyManagementExtension.initialize e este store conterá o bean de teste que anotamos. + //Nós então disparamos o evento de shutdown onde ele deverá limpar o store. + Assert.assertNotNull(store.getManagedTypes()); + Assert.assertEquals(1, store.getManagedTypes().size()); + + Beans.getBeanManager().fireEvent(new AfterShutdownProccess() {}); + Assert.assertNull(store.getManagedTypes()); + } + +} diff --git a/impl/core/src/test/java/test/Tests.java b/impl/core/src/test/java/test/Tests.java index 95ae861..b16ce3c 100644 --- a/impl/core/src/test/java/test/Tests.java +++ b/impl/core/src/test/java/test/Tests.java @@ -39,7 +39,6 @@ package test; import java.io.File; import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.asset.EmptyAsset; import org.jboss.shrinkwrap.api.asset.FileAsset; import org.jboss.shrinkwrap.api.spec.JavaArchive; diff --git a/impl/core/src/test/resources/test/beans.xml b/impl/core/src/test/resources/test/beans.xml index 72cfc7c..d85497f 100644 --- a/impl/core/src/test/resources/test/beans.xml +++ b/impl/core/src/test/resources/test/beans.xml @@ -2,10 +2,10 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> + br.gov.frameworkdemoiselle.transaction.TransactionalInterceptor + br.gov.frameworkdemoiselle.security.RequiredPermissionInterceptor + br.gov.frameworkdemoiselle.security.RequiredRoleInterceptor br.gov.frameworkdemoiselle.exception.ExceptionHandlerInterceptor - br.gov.frameworkdemoiselle.internal.interceptor.RequiredPermissionInterceptor - br.gov.frameworkdemoiselle.internal.interceptor.RequiredRoleInterceptor - br.gov.frameworkdemoiselle.internal.interceptor.TransactionalInterceptor diff --git a/impl/extension/jsf/src/main/java/br/gov/frameworkdemoiselle/internal/context/ViewContext.java b/impl/extension/jsf/src/main/java/br/gov/frameworkdemoiselle/internal/context/ViewContext.java index 4ad5c6f..87f8e5c 100644 --- a/impl/extension/jsf/src/main/java/br/gov/frameworkdemoiselle/internal/context/ViewContext.java +++ b/impl/extension/jsf/src/main/java/br/gov/frameworkdemoiselle/internal/context/ViewContext.java @@ -44,7 +44,7 @@ import br.gov.frameworkdemoiselle.util.Faces; public class ViewContext extends AbstractCustomContext { public ViewContext() { - super(ViewScoped.class, true); + super(ViewScoped.class); } @Override -- libgit2 0.21.2