Web Concepts

Cedrick Lunven edited this page Aug 22, 2018 · 12 revisions

Elements presented in this page are provided through the ff4j-web-* module. You will need to add thoses dependencies to your pom.xml file.

<dependency>
 <groupId>org.ff4j</groupId>
 <artifactId>ff4j-web</artifactId>
 <version>${current_ff4j_version}</version>
</dependency>

Web Console

FF4J provides you web administration console in order to administrate features, properties and monitoring at runtime. They are implemented as a single servlet. You need to declare the servlet in the web.xml for J2E webapp or declare the servlet as a bean for spring-boot based applications.

Provide a FF4jProvider

You must provide a FF4JProvider. It's a way to inject an instance of the FF4J class in the target servlet (not every application use the inversion of control pattern). You can here find a sample.

package org.ff4j.sample;
import org.ff4j.FF4j;
import org.ff4j.web.api.FF4JProvider;

public class DummyFF4JProvider implements FF4JProvider {
 
    private final FF4j ff4j;
    
    /** Default constructor is required as class will be instanciated with Reflection. */
    public MyFF4jProvider() {
        // Or use a Singleton class you created
        ff4j = new FF4j("ff4j.xml");
    }
  
    /** Method expected by Interface FF4JProvider  */
    public FF4j getFF4j() { return ff4j; }
}

Declare the servlet (before version 1.6)

If you use a J2E application, edit your web.xml file in the following way:

 <servlet>
  <servlet-name>ff4j-console</servlet-name>
  <servlet-class>org.ff4j.web.embedded.ConsoleServlet</servlet-class>
  <init-param>
   <param-name>ff4jProvider</param-name>
   <param-value>org.ff4j.sample.DummyFF4JProvider </param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 
 <servlet-mapping>
   <servlet-name>ff4j-console</servlet-name>
   <url-pattern>/ff4j-console</url-pattern>
  </servlet-mapping>

You should be able to display something like: First Console

Declare the servlet (@Since 1.6)

<servlet>
 <servlet-name>ff4j-console</servlet-name>
 <servlet-class>org.ff4j.web.FF4jDispatcherServlet</servlet-class>
 <init-param>
  <param-name>ff4jProvider</param-name>
  <param-value>org.ff4j.sample.DummyFF4JProvider</param-value>
 </init-param>
 <load-on-startup>1</load-on-startup>
</servlet>
	
<servlet-mapping>
 <servlet-name>ff4j-console</servlet-name>
 <url-pattern>/ff4j-console/*</url-pattern>
</servlet-mapping>

You will get something like: New Console Home

With new features: New Console Feature

New Console Monitoring

Securing WebConsole

WebConsole does not provide anything by itself, as it's a single servlet, the authentication must be handled aside. Spring security is a good candidate but any secure servlet security mecanism would be correct : Filter or security constraints in web.xml. So far they are no Filter provided out-of-the-box. In FF4j 2X there will be a user management than can leverage on external solutions.

TagLib

Taglib are tags to override default behaviour and generate custom parts in all kind of JSP pages. The most know are JSTL or Spring-Security or in older ages struts. In feature toggle that can be use to show and hide part of the views/screen base on status of a feature.

Taglibs are defined in a XML file named tld (here for ff4j) and a related namespace. The namespace reference file must be included in the JSP where you use tags. In can be the first line of JSP with import command on included in dedicated JSP (ex:header.jsp) where you will found all the definition.

FF4j taglib is defined in the ff4j-web module. The tags defined in this library expect to find the instance of FF4j object available in the application scope. (application, session or request actually). If you declare the embedded servlet in your web.xmlfile, this declaration is made for you. Otherwise you have to put this code somewere in your servlet initialization :

FF4j ff4j = ...;
servletConfig.getServletContext().setAttribute("FF4J", ff4j);

To use the tag library it's required to Add to the JSP the Taglib definition (could be wrapped in includes if using sitemesh).

<%@ taglib prefix="ff4j" uri="http://www.ff4j.org/taglibs/ff4j" %>
<!-- First Line -->

Now here are some usages of the tag :

<ff4j:enable featureid="myID">
  This code is displ	yed onlyif the myId is UP
</ff4j:enable>

<ff4j:disable featureid="<=% org.sample.MyFooClass.BAR_CONSTANT %>">
  This code is displ	yed onlyif the myId is DOWN
</ff4j:disable>

To implement from FlippingStrategy in the Taglib there is an optional attribute named shareHttpSession. If the flag is enabled, all keys in HttpRequest context are copied into the FlippingExecutionContext'. For instance, if you use theReleaseDateFlipStrategy`in one of your feature you can have

pageContext.getRequest().setAttribute("releaseDate", "2017-11-20-13:59");

and in the JSP :

<ff4j:enable featureid="myID" shareHttpSession="true">
 hiyaaa
</ff4j:enable>

Thymeleaf

The web console is developped using ThymeLeaf. As for TagLib FF4j provide a set of custom tags to display or hide part of the screen depending on the status of a feature.

REST Api

Operations to administrate features/properties/monitoring are available through REST API. It exposes a swagger2x contract to allow easy integration with Javascript, mobile or any client applications. Using SwaggerUI we provide a browsing documentation here http://ff4j.org/rest-api

There are 3 implementations of this REST API : Jersey1x, Jersey2x and SpringMVC) you can pick the one you want depending on the current dependencies of your application. (nb:In the SpringBoot starter we chose the SpringMVC)

To make the Servlet Api available in your application please follow the steps. You will see that API can be secured through APIKey or Basic Authentification. It's a custom implementation with RequestFilters. To consume the API clients are already provided as FeatureStoreHttp and PropertyStoreHttp.

Jersey1x

Please find here the workflow to expose REST API using the Jersey1x implementation.

public class SimpleFF4JJerseyApplication extends FF4JApiApplication implements FF4jProvider {

    private FF4j ff4j = null;
    
    private ApiConfig apiConfig = null;
    
    /** {@inheritDoc} */
    @Override
    public FF4j getFF4j() {
        if (ff4j == null) {
            // init FF4J from spring, static, singleton as you wish
        }
        return ff4j;
    }

    /** {@inheritDoc} */
    @Override
    protected ApiConfig getApiConfig() {
        if (apiConfig == null) {
            apiConfig = new ApiConfig(getFF4j());
            apiConfig.setDocumentation(true);

            // Optional
            apiConfig.setAuthenticate(true);
            apiConfig.setAutorize(true);
            apiConfig.createUser("admin", "admin", true, true, null);
            apiConfig.createUser("user", "user", true, true,  Util.set("ADMIN", "USER"));
            apiConfig.createApiKey("12345", false, false, Util.set("ADMIN", "USER"));
        }
        
        return apiConfig;
    }
    
  • Update the web.xmlfile in order to declare the servlet
<servlet>
 <servlet-name>WebAPI</servlet-name>
 <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
 <init-param>
  <param-name>javax.ws.rs.Application</param-name>
  <param-value>org.ff4j.demo.SimpleFF4JJerseyApplication</param-value>
  </init-param>
 </servlet>
<servlet-mapping>
 <servlet-name>WebAPI</servlet-name>
 <url-pattern>/api/*</url-pattern>
</servlet-mapping>

Jersey2x

Please find here the workflow to expose REST API using the Jersey2x implementation.

@Configuration
@ApplicationPath("/api")
public class FF4JSampleWebApi extends FF4jApiApplicationJersey2x {
    
    @Autowired
    public ApiConfig apiConfig;
   
    @Override
    public ApiConfig getWebApiConfiguration() {
        return apiConfig;
    }
    
    @PostConstruct
    public void initialized() {
        super.init();
    }
}

Spring MVC

Here all ressources are injected into SpringContext if the package name is part of the component scan. org.ff4j.spring.boot.web.api The best way to go is maybe to leverage on spring-boot-starter.

@Configuration
@ConditionalOnClass({FF4j.class})
@ComponentScan(value = {"org.ff4j.spring.boot.web.api", "org.ff4j.services", "org.ff4j.aop", "org.ff4j.spring"})
public class FF4JConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public FF4j getFF4j() {
        return new FF4j();
    }
}

Securing accesses to API

The different implementations of REST API can be secured by using some ApiKey of Basic Authentication. Sample here : https://github.com/clun/ff4j/blob/master/ff4j-webapi-jersey2x/src/test/java/org/ff4j/web/api/test/it/SecuredJersey2Application.java

//[..] ff4j definition

ApiConfig apiCfg= new ApiConfig(ff4j);
apiCfg.setAuthenticate(true);
apiCfg.setAutorize(true);

// Sample to Create APIKey
boolean aclAllowRead = true;
boolean aclAllowWrite = true;
Set < String > setofRoles = new HashSet<>();
setofRoles .add("USER");
setofRoles .add("ADMIN");
apiCfg.createApiKey("sampleAPIKey1234567890", aclAllowRead , aclAllowWrite , setofRoles );

// Sample to Create User/password
apiCfg.createUser("myLogin", "myPassword", aclAllowRead , aclAllowWrite , setofRoles );

Accessing a secured API :

FF4j ff4jClient = new FF4j();
ff4jClient .setFeatureStore(new FeatureStoreHttp("http://localhost:9998/ff4j", "apiKey"));
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.