package org.ff4j.core;
/*
* #%L ff4j-core %% Copyright (C) 2013 Ff4J %% Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License. #L%
*/
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.ff4j.exception.PropertyNotFoundException;
import org.ff4j.property.Property;
import org.ff4j.property.util.PropertyFactory;
import org.ff4j.utils.JsonUtils;
import org.ff4j.utils.MappingUtil;
import org.ff4j.utils.Util;
/**
* Represents a feature flag identified by an unique identifier.
*
* <p>
* Features Flags or Features Toggle have been introduced by Martin Fowler for continuous delivery perspective. It consists of
* enable/disable some functionalities at runtime.
*
* <p>
* <b>SecurityManagement :</b> Even a feature is enabled, you can limit its usage to a group of users (for instance BETA Tester)
* before wide over all your users.
* </p>
*
* @author Cedrick Lunven (@clunven)
*/
public class Feature implements Serializable {
/** serial of the class. */
private static final long serialVersionUID = -1345806526991179050L;
/** Unique Feature Identifier */
private String uid;
/** Status of target feature, can be enable and disable. */
private boolean enable = false;
/** Short description of the feature, use it for information. */
private String description;
/** Feature could be grouped to enable/disable the whole group. */
private String group;
/** if not empty and @see {@link org.ff4j.security.AuthorizationsManager} provided, limit usage to this roles. */
private Set<String> permissions = new TreeSet<String>();
/** Custom behaviour to define if feature if enable or not e.g. A/B Testing capabilities. */
private FlippingStrategy flippingStrategy;
/** Add you own attributes to a feature. */
private Map < String, Property<?> > customProperties = new LinkedHashMap<String, Property<?>>();
/**
* Simplest constructor initializing feature to disable.
*
* @param uid
* unique feature name (required)
*/
public Feature(final String uid) {
this(uid, false, null);
}
/**
* Simple constructor initializing feature with status enable/disable.
*
* @param uid
* unique feature name (required)
* @param penable
* initial feature state
*/
public Feature(final String uid, final boolean penable) {
this(uid, penable, null);
}
/**
* Simplest Constructor (without security concerns)
*
* @param uid
* unique feature name (required)
* @param penable
* initial feature state
* @param pdescription
* description of feature.
*/
public Feature(final String uid, final boolean penable, final String pdescription) {
if (uid == null || uid.isEmpty()) {
throw new IllegalArgumentException("Feature identifier (param#0) cannot be null nor empty");
}
this.uid = uid;
this.enable = penable;
this.description = pdescription;
}
/**
* Simplest Constructor (without security concerns)
*
* @param uid
* unique feature name (required)
* @param penable
* initial feature state
* @param pdescription
* description of feature.
*/
public Feature(final String uid, final boolean penable, final String pdescription, final String group) {
this(uid, penable, pdescription);
if (group != null && !"".equals(group)) {
this.group = group;
}
}
/**
* Constructor with limited access roles definitions
*
* @param uid
* unique feature name (required)
* @param penable
* initial feature state
* @param pdescription
* description of feature.
* @param auths
* limited roles to use the feature even if enabled
*/
public Feature(final String uid, final boolean penable, final String pdescription, final String group,
final Collection<String> auths) {
this(uid, penable, pdescription, group);
if (auths != null && !auths.isEmpty()) {
this.permissions = new HashSet<String>(auths);
}
}
/**
* Constructor with limited access roles definitions
*
* @param uid
* unique feature name (required)
* @param penable
* initial feature state
* @param pdescription
* description of feature.
* @param auths
* limited roles to use the feature even if enabled
*/
public Feature(final String uid, final boolean penable, final String pdescription, final String group,
final Collection<String> auths, final FlippingStrategy strat) {
this(uid, penable, pdescription, group, auths);
if (strat != null) {
this.flippingStrategy = strat;
}
}
/**
* Copy constructor.
*
* @param f
* current feature
*/
public Feature(final Feature f) {
this(f.getUid(), f.isEnable(), f.getDescription(), f.getGroup());
this.permissions.addAll(f.getPermissions());
// Flipping Strategy
if (f.getFlippingStrategy() != null) {
this.flippingStrategy = MappingUtil.instanceFlippingStrategy(f.getUid(),
f.getFlippingStrategy().getClass().getName(),
f.getFlippingStrategy().getInitParams());
}
// Custom Properties
if (f.getCustomProperties() != null && !f.getCustomProperties().isEmpty()) {
for(Property<?> p : f.getCustomProperties().values()) {
Property<?> targetProp = PropertyFactory.createProperty(
p.getName(), p.getType(), p.asString(), p.getDescription(), null);
if (p.getFixedValues() != null) {
for(Object o : p.getFixedValues()) {
targetProp.add2FixedValueFromString(o.toString());
}
}
this.getCustomProperties().put(targetProp.getName(), targetProp);
}
}
}
/** {@inheritDoc} */
@Override
public String toString() {
return toJson();
}
/**
* Convert Feature to JSON.
*
* @return target json
*/
public String toJson() {
StringBuilder json = new StringBuilder("{");
json.append("\"uid\":\"" + uid + "\"");
json.append(",\"enable\":" + enable);
json.append(",\"description\":");
json.append((null == description) ? "null" : "\"" + description + "\"");
json.append(",\"group\":");
json.append((null == group) ? "null" : "\"" + group + "\"");
// Permissions
json.append(",\"permissions\":" + JsonUtils.permissionsAsJson(permissions));
// Flipping strategy
json.append(",\"flippingStrategy\":" + JsonUtils.flippingStrategyAsJson(flippingStrategy));
// Custom properties
json.append(",\"customProperties\":" + JsonUtils.customPropertiesAsJson(customProperties));
json.append("}");
return json.toString();
}
public static Feature fromJson(String jsonString) {
return null;
}
/**
* Enable target feature
*/
public void enable() {
this.enable = true;
}
/**
* Disable target feature
*/
public void disable() {
this.enable = false;
}
/**
* Toggle target feature (from enable to disable and vice versa)
*/
public void toggle() {
this.enable = !this.enable;
}
/**
* Getter accessor for attribute 'uid'.
*
* @return current value of 'uid'
*/
public String getUid() {
return uid;
}
/**
* Setter accessor for attribute 'uid'.
*
* @param uid
* new value for 'uid '
*/
public void setUid(String uid) {
this.uid = uid;
}
/**
* Getter accessor for attribute 'enable'.
*
* @return current value of 'enable'
*/
public boolean isEnable() {
return enable;
}
/**
* Setter accessor for attribute 'enable'.
*
* @param enable
* new value for 'enable '
*/
public void setEnable(boolean enable) {
this.enable = enable;
}
/**
* Getter accessor for attribute 'description'.
*
* @return current value of 'description'
*/
public String getDescription() {
return description;
}
/**
* Setter accessor for attribute 'description'.
*
* @param description
* new value for 'description '
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Getter accessor for attribute 'flippingStrategy'.
*
* @return current value of 'flippingStrategy'
*/
public FlippingStrategy getFlippingStrategy() {
return flippingStrategy;
}
/**
* Setter accessor for attribute 'flippingStrategy'.
*
* @param flippingStrategy
* new value for 'flippingStrategy '
*/
public void setFlippingStrategy(FlippingStrategy flippingStrategy) {
this.flippingStrategy = flippingStrategy;
}
/**
* Getter accessor for attribute 'group'.
*
* @return current value of 'group'
*/
public String getGroup() {
return group;
}
/**
* Setter accessor for attribute 'group'.
*
* @param group
* new value for 'group '
*/
public void setGroup(String group) {
this.group = group;
}
/**
* Getter accessor for attribute 'permissions'.
*
* @return current value of 'permissions'
*/
public Set<String> getPermissions() {
return permissions;
}
/**
* Setter accessor for attribute 'permissions'.
*
* @param permissions
* new value for 'permissions '
*/
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
/**
* Accessor to read a custom property from Feature.
*
* @param propId
* property
* @return
* property value (if exist)
*/
@SuppressWarnings("unchecked")
public <T> Property<T> getProperty(String propId) {
Util.assertNotNull(propId);
if (customProperties != null && customProperties.containsKey(propId)) {
return (Property<T>) customProperties.get(propId);
}
throw new PropertyNotFoundException(propId);
}
/**
* Utility to add a property.
*
* @param props
*/
public <T> void addProperty(Property< T > props) {
Util.assertNotNull(props);
if (customProperties == null) {
customProperties = new LinkedHashMap<String, Property<?>>();
}
customProperties.put(props.getName(), props);
}
/**
* Getter accessor for attribute 'customProperties'.
*
* @return
* current value of 'customProperties'
*/
public Map<String, Property<?>> getCustomProperties() {
return customProperties;
}
/**
* Setter accessor for attribute 'customProperties'.
* @param customProperties
* new value for 'customProperties '
*/
public void setCustomProperties(Map<String, Property<?>> customProperties) {
this.customProperties = customProperties;
}
}