Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

...

See Pre-required readings

Further readings:

Usage for Users

ProjectStage

...

The core provides a pluggable and type-safe approach for using project stages in a project (it's also used within the framework). Furthermore, @ProjectStageActivated allows to use e.g. implementations annotated with javax.enterprise.inject.Alternative for specific project-stages. Besides the out-of-the-box project-stages it's possible to implement custom but type-safe project-stages which will be exposed by CODI.

Code Block
javajava
titleResolving and using the Project-Stage
java
@Inject
private ProjectStage projectStage;

//...

boolean isDevProjectStage = ProjectStage.Development.equals(this.projectStage);

...

This annotation allows to activate beans for a special project-stage. It's possible to use one of the out-of-the-box project-stages or a custom typesafe project-stage.

Code Block
javajava
titleAlternative implementation activated for Project-Stage UnitTest
java
@javax.enterprise.inject.Alternative
@ProjectStageActivated(ProjectStage.UnitTest.class)
public class TestServiceMockImpl implements Service
{
  //...
}
Code Block
javajava
titleAlternative implementation activated for Project-Stages UnitTest and Development
java
@Alternative
@ProjectStageActivated({ProjectStage.UnitTest.class, ProjectStage.Development.class})
public class DevServiceMockImpl implements Service
{
  //...
}

...

This annotation allows to activate beans based on expressions. Out-of-the-box simple conditions are supported. The values will be resolved from the environment via the default resolvers (see out-of-the-box environment-config options) or via a custom ConfiguredValueResolver which allows to use any type of configuration-format.

java
Code Block
java
titleAlternative implementation activated based on a configured value
java
@ExpressionActivated("db==dev-db")
public class TestDataCreator
{
  //...
}

or

@ExpressionActivated("db!=prod-db;db==*")
public class TestDataCreator
{
  //...
}

...

Per default a simple syntax for key/value based configs is supported. However, it's possible to implement a custom interpreter for custom expressions.

Code Block
javajava
titleA custom interpreter for custom expressions
java
@ExpressionActivated(value = "${environment.stage eq 'test'}", interpreter = CustomInterpreter.class)
public class TestDataCreator
{
  //...
}

...

In some cases it isn't possible to use CDI based configuration. Esp. for parts which have to be active before the CDI container gets bootstrapped.
For such cases CODI uses an extensible configuration approach. By default it supports configuration via:

  • ServiceLoader (if classes have to be configured which implement an interface or extend an abstract class)
  • System properties
  • JNDI
  • Resource Bundle (properties file with the name myfaces-extcdi.properties btw. /META-INF/myfaces-extcdi.properties)

However, with a custom ConfiguredValueResolver it's possible to resolve configured values from any kind of configuration-source.

...

To avoid external dependencies, CODI uses the JDK Logger. However, CDI often requires serializable beans and JDK loggers aren't serializable - therefore CODI provides a serializable wrapper for the JDK Logger.

java
Code Block
java
titleInjectable JDK logger
java
public class MyBean
{
    @Inject
    private Logger logger;
}

By default the fully qualified class name of the target class which uses the injected logger, will be used to create the logger. As an alternative it's possible to use the LoggerDetails qualifier to provide e.g. a name for the logger.

java
Code Block
java
titleInjectable JDK logger with a custom name
java
public class MyBean
{
    @Inject
    @LoggerDetails(name = "AppLogger")
    private Logger logger;
}

...

BeanManagerProvider

Code Block
javajava
titleResolving the Bean-Manager
java
@Inject
private BeanManager beanManager;

//or

BeanManager beanManager = BeanManagerProvider.getInstance().getBeanManager();

...

CODI provides multiple hooks for the startup. Usually it's enough to observe the StartupEvent fired by CODI during the startup-process as soon as the target environment is up and running. In case of JSF this event is fired lazily. If you need to execute custom logic before CODI gets active, you should have a look at the dev guide (see StartupEventBroadcaster).

java
Code Block
java
titleObserving the startup event
java
@ProjectStageActivated({Development.class, IntegrationTest.class})
public class SampleDataStartupObserver
{
    protected void createSampleData(@Observes StartupEvent startupEvent, UserRepository userRepository)
    {
        User user = new User("Demo", "User");
        userRepository.save(user);
    }
}

...

@Bundle allows to inject a ResourceBundle. This interface is provided by CODI and is a simpler but injectable version of the std. ResourceBundle.

java
Code Block
java
titleInjecting and using a resource-bundle
java
import org.apache.myfaces.extensions.cdi.core.api.resource.bundle.ResourceBundle;

//...
public class MyBean
{
    @Inject
    //@Jsf //optional to use the current locale
    @Bundle(MyBundle.class)
    private ResourceBundle resourceBundle;

    public String getMyValue()
    {
        return this.resourceBundle.getValue(MyBundle.MyKey.class);
    }
}

By default a resource-bundle class/interface is mapped to the corresponding bundle-file via naming convention. A class/interface can be annotated with @Bundle optionally to find it easier via searching for the annotation or to changing the name and/or package of the corresponding bundle-file.

Code Block
javajava
titleImplementing a resource-bundle
java
package mypackage.myconfig;

//@Bundle //optional in this case
//Bundle gets mapped to mypackage.myconfig.my_bundle.properties
public interface MyBundle
{
    //mapped to the resource-bundle key (by naming convention): my_key
    public class MyKey implements BundleKey, MyBundle {}

    //mapped to the resource-bundle key (manually): secondKey
    @Named("secondKey")
    public class MyKey2 extends BundleValue implements MyBundle {}
}

...

Instead of injecting the resource-bundle and resolving a value by key, it's possible to inject the value directly. That's e.g. useful for configs, because in such cases you are interested in few very specific values.

java
Code Block
java
titleInjecting a resource-bundle value
java
@Bundle(name = "mypackage.myconfig.mybundle")
public interface MyBundle
{
    //mapped to the resource-bundle key (by naming convention): my_value
    //@Named("myKey") //for mapping it to the resource-bundle key (manually): myKey
    public class MyValue extends BundleValue implements Messages {}
}

//...
public class MyBean
{
    @Inject
    private MyBundle.MyValue myValue;

    public String getMyValue()
    {
        return this.myValue.toString();
    }
}

...

For creating instances of Annotations, you can use the literal trick. A custom implementation allows to provide custom values (see e.g. the NamedLiteral which is used by CODI internally). If you are fine with the default values of an annotation, you can use DefaultAnnotation to create an annotation for a given type, instead of a custom literal implementation.

Code Block
javajava
titleCreate instances of Annotations
java
CustomAnnotation annotation = DefaultAnnotation.of(CustomAnnotation.class);

...

This annotation allows to provide custom meta-data. Just annotate a custom annotation with it. A module like the JSF module has to provide a resolver to query it. A query might return multiple results of the same type. If it doesn't make sense to have multiple results, you can use @ViewMetaData(override=true).

Code Block
javajava
titleCustom Meta-data for View-Configs
java
@Target({TYPE})
@Retention(RUNTIME)
@Documented

@ViewMetaData
public @interface InfoPage
{
}

...

This navigation handler defines how to navigate with view-configs. Currently it's used by all JSF modules to allow implicit navigation based on the view-configs.

java
Code Block
java
titleObserving the navigation
java
@Model
public class ManualNavigationBean
{
    @Inject
    private ViewNavigationHandler viewNavigationHandler;

    public void navigateToHelloMyFacesCodi(ActionEvent actionEvent)
    {
        this.viewNavigationHandler.navigateTo(DemoPages.HelloMyFacesCodi.class);
    }

    public void navigateToErrorView(ActionEvent actionEvent)
    {
        this.viewNavigationHandler.navigateTo(DefaultErrorView.class);
    }
}

...

This event gets triggered if a navigation is going to happen from and to a page represented by a view-config. It also allows to redefine the navigation target.

Code Block
javajava
titleObserving the navigation
java
@Model
public class ViewConfigNavigationObserver
{
    @Inject
    //@Jsf //just in case of a JSF application
    private MessageContext messageContext;

    protected void onViewConfigNavigation(@Observes PreViewConfigNavigateEvent navigateEvent)
    {
        if(/*...*/)
        {
            navigateEvent.navigateTo(DefaultErrorView.class);
        }

        this.messageContext.message()
                                .text("navigate from {oldViewId} to {newViewId} view.")
                                .namedArgument("oldViewId", navigateEvent.getFromView())
                                .namedArgument("newViewId", navigateEvent.getToView())
                           .add();
    }
}

...

The following events are optional and have to be activated via the configs mentioned above.

  • AccessBeanEvent
  • CloseConversationEvent
  • CloseWindowContextEvent
  • CreateWindowContextEvent
  • RestartConversationEvent
  • ScopeBeanEvent
  • StartConversationEvent
  • UnscopeBeanEvent

Security

CODI provides some basic hooks to integrate a custom security concept. It isn't related to an existing framework. CODI just allows to integrate such frameworks based on the ViewConfig or via interceptors.

...

The AbstractAccessDecisionVoter allows an easier implementation.

@Secured

java
Code Block
java
titleSimple usage of @Secured for ViewConfigs
java
@Page(navigation = REDIRECT)
public interface Pages extends ViewConfig
{
    @Secured(LoginAccessDecisionVoter.class)
    public interface Secure extends Pages
    {
        public @Page class InternalPage implements Secure {}
    }
}

In case of a violation CODI will use the DefaultErrorView as navigation target (if configured).

java
Code Block
java
titleSimple usage of @Secured for ViewConfigs with a special error page
java
@Page(navigation = REDIRECT)
public interface Pages extends ViewConfig
{
    @Secured(value = LoginAccessDecisionVoter.class, errorView = Login.class)
    public interface Secure extends Pages
    {
        public @Page class InternalPage implements Secure {}
    }
}

...

As an alternative it's possible to use the annotation as interceptor for beans.

Code Block
javajava
titleAlternative usage of @Secured as interceptor
java
@RequestScoped
@Secured(LoginAccessDecisionVoter.class)
public class SecureBean
{
    //...
}

...

In case of a detected violation a SecurityViolation has to be added to the result returned by the AccessDecisionVoter.

java
Code Block
java
titleSimple example for creating a SecurityViolation
java
@ApplicationScoped
public class LoginAccessDecisionVoter extends AbstractAccessDecisionVoter
{
    private static final long serialVersionUID = -6332617547592896599L;

    @Inject
    private UserHolder userHolder;

    @Inject
    //@Jsf //only required in combination with the JSF module
    private MessageContext messageContext;

    @Override
    protected void checkPermission(InvocationContext invocationContext, Set<SecurityViolation> violations)
    {
        if(!this.userHolder.isLoggedIn())
        {
            violations.add(newSecurityViolation(this.messageContext.message().text("{msgAccessDenied}").toText()));
        }
    }
}

...

If there are multiple AccessDecisionVoter and maybe in different constellations, it's easier to provide an expressive CDI stereotypes for it. Later on that also allows to change the behaviour in a central place.

java
Code Block
java
titleStereotype support of @Secured
java
@Named
@Admin
public class MyBean implements Serializable
{
  //...
}

//...
@Stereotype
@Secured(RoleAccessDecisionVoter.class)
public @interface Admin
{
} 

Furthermore, it's possible to provide custom meta-data easily.

Code Block
javajava
titleStereotype of @Secured with custom meta-data
java
@Named
@Admin(securityLevel=3)
public class MyBean implements Serializable
{
  //...
}

//...
@Stereotype
@Secured(RoleAccessDecisionVoter.class)
public @interface Admin
{
  int securityLevel();
}

@ApplicationScoped
public class RoleAccessDecisionVoter implements AccessDecisionVoter
{
    private static final long serialVersionUID = -8007511215776345835L;
    
    @Inject
    private AccessDecisionVoterContext voterContext;

    public Set<SecurityViolation> checkPermission(InvocationContext invocationContext)
    {
        Admin admin = voterContext.getMetaDataFor(Admin.class.getName(), Admin.class);
        int level = admin.securityLevel();
        //...
    }
} 

...