Web Concepts - ff4j/ff4j GitHub Wiki
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>
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.
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; }
}
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:
<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:
With new features:
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 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.xml
file, 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 the
ReleaseDateFlipStrategy`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>
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.
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
.
Please find here the workflow to expose REST API using the Jersey1x implementation.
- Create a class extending
org.ff4j.web.api.FF4JApiApplication
and implement the 2 methods (sample here https://github.com/clun/ff4j-samples/blob/master/ff4j-demo/src/main/java/org/ff4j/demo/SimpleFF4JJerseyApplication.java)
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.xml
file 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>
Please find here the workflow to expose REST API using the Jersey2x implementation.
- Create a class extending
org.ff4j.web.api.FF4jApiApplicationJersey2x
and implement the 2 methods (sample here https://github.com/clun/ff4j-samples/tree/4ba55882b1ead64eb6ea6544f53f491c03238c74/ff4j-sample-archaius/src/main/java/org/ff4j/springboot)
@Configuration
@ApplicationPath("/api")
public class FF4JSampleWebApi extends FF4jApiApplicationJersey2x {
@Autowired
public ApiConfig apiConfig;
@Override
public ApiConfig getWebApiConfiguration() {
return apiConfig;
}
@PostConstruct
public void initialized() {
super.init();
}
}
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();
}
}
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 );
FF4j ff4jClient = new FF4j();
ff4jClient .setFeatureStore(new FeatureStoreHttp("http://localhost:9998/ff4j", "apiKey"));