All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ff4j.web.api.resources.FeatureResource Maven / Gradle / Ivy

There is a newer version: 1.7.1
Show newest version
package org.ff4j.web.api.resources;

/*
 * #%L
 * ff4j-web
 * %%
 * Copyright (C) 2013 - 2014 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.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.ff4j.core.Feature;
import org.ff4j.exception.FeatureNotFoundException;
import org.ff4j.utils.MappingUtil;
import org.ff4j.web.FF4jWebConstants;
import org.ff4j.web.api.resources.domain.FeatureApiBean;
import org.ff4j.web.api.resources.domain.FlippingStrategyApiBean;
import org.ff4j.web.api.resources.domain.PropertyApiBean;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;

import static org.ff4j.web.FF4jWebConstants.*;

/**
 * Represent a feature as WebResource.
 * 
 * @author Cedrick LUNVEN
 */
@Path("/ff4j/store/features/{uid}")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed({FF4jWebConstants.ROLE_WRITE})
@Api(value = "/ff4j/store/features/{uid}")
public class FeatureResource extends AbstractResource {
  
    /**
     * Allows to retrieve feature by its id.
     * 
     * @param featId
     *            target feature identifier
     * @return feature is exist
     */
    @GET
    @RolesAllowed({ROLE_READ})
    @Produces(MediaType.APPLICATION_JSON)
    @ApiOperation(value= "Read information about a feature", response=FeatureApiBean.class)
    @ApiResponses({
        @ApiResponse(code = 200, message= "Information about features"), 
        @ApiResponse(code = 404, message= "Feature not found") })
    public Response read(@PathParam("uid") String id) {
       if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
       }
       return  Response.ok(new FeatureApiBean(ff4j.getFeatureStore().read(id))).build();
    }

    /**
     * Create the feature if not exist or update it
     * 
     * @param headers
     *            current request header
     * @param data
     *            feature serialized as JSON
     * @return 204 or 201
     */
    @PUT
    @RolesAllowed({ROLE_WRITE})
    @ApiOperation(value= "Create of update a feature", response=Response.class)
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @ApiResponses({
        @ApiResponse(code = 201, message= "Feature has been created"), 
        @ApiResponse(code = 204, message= "No content, feature is updated") })
    public Response upsertFeature(@Context HttpHeaders headers, @PathParam("uid") String id, FeatureApiBean fApiBean) {
        // Parameter validations
        if ("".equals(id) || !id.equals(fApiBean.getUid())) {
            String errMsg = "Invalid identifier expected " + id;
            return Response.status(Response.Status.BAD_REQUEST).entity(errMsg).build();
        }
        
        Feature feat = new Feature(id);
        feat.setDescription(fApiBean.getDescription());
        feat.setEnable(fApiBean.isEnable());
        feat.setGroup(fApiBean.getGroup());
        feat.setPermissions(new HashSet(fApiBean.getPermissions()));
        // Flipping Strategy
        FlippingStrategyApiBean flipApiBean = fApiBean.getFlippingStrategy();
        if (flipApiBean != null) {
            try {
                Map initparams = flipApiBean.getInitParams();
                feat.setFlippingStrategy(MappingUtil.instanceFlippingStrategy(id, flipApiBean.getType(), initparams));
            } catch (Exception e) {
                String errMsg = "Cannot read Flipping Strategy, does not seems to have a DEFAULT constructor, " + e.getMessage();
                return Response.status(Response.Status.BAD_REQUEST).entity(errMsg).build();
            }
        }
        // Properties
        Map mapProperties = fApiBean.getCustomProperties();
        if (mapProperties != null) {
            for(PropertyApiBean propertyBean : mapProperties.values()) {
                feat.addProperty(propertyBean.asProperty());
            }       
        }
        
        // Update or create ? 
        if (!getFeatureStore().exist(feat.getUid())) {
            getFeatureStore().create(feat);
            String location = String.format("%s", uriInfo.getAbsolutePath().toString());
            try {
                return Response.created(new URI(location)).build();
            } catch (URISyntaxException e) {
                return Response.status(Response.Status.CREATED).header(LOCATION, location).entity(id).build();
            }
        }
        
        // Create
        getFeatureStore().update(feat);
        return Response.noContent().build();
    }

    /**
     * Delete feature by its id.
     * 
     * @return delete by its id.
     */
    @DELETE
    @RolesAllowed({ROLE_WRITE})
    @Produces(MediaType.TEXT_PLAIN)
    @ApiOperation(value= "Delete a feature", response=Response.class)
    @ApiResponses({
        @ApiResponse(code = 404, message= "Feature has not been found"), 
        @ApiResponse(code = 204, message= "No content, feature is deleted"),
        @ApiResponse(code = 400, message= "Bad identifier"),
        })
    public Response deleteFeature(@PathParam("uid") String id) {
        if (id == null || "".equals(id)) {
            String errMsg = "Invalid URL : Must be '/features/{uid}' with {uid} not null nor empty";
            return Response.status(Response.Status.BAD_REQUEST).entity(errMsg).build();
        }
        if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
        }
        getFeatureStore().delete(id);
        return Response.noContent().build();
    }

    /**
     * Convenient method to update partially the feature: Here enabling
     * 
     * @return http response.
     */
    @POST
    @Path("/" + OPERATION_ENABLE)
    @RolesAllowed({ROLE_WRITE})
    @ApiOperation(value= "Enable a feature", response=Response.class)
    @ApiResponses({
        @ApiResponse(code = 204, message= "Features has been enabled"), 
        @ApiResponse(code = 404, message= "Feature not found") })
    public Response operationEnable(@PathParam("uid") String id) {
        if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
        }
        getFeatureStore().enable(id);
        return Response.noContent().build();
    }

    /**
     * Convenient method to update partially the feature: Here disabling
     * 
     * @return http response.
     */
    @POST
    @Path("/" + OPERATION_DISABLE)
    @RolesAllowed({ROLE_WRITE})
    @ApiOperation(value= "Disable a feature", response=Response.class)
    @ApiResponses({
        @ApiResponse(code = 204, message= "Features has been disabled"), 
        @ApiResponse(code = 404, message= "Feature not found") })
    public Response operationDisable(@PathParam("uid") String id) {
        if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
        }
        getFeatureStore().disable(id);
        return Response.noContent().build();
    }

    /**
     * Convenient method to update partially the feature: Here grant a role
     * 
     * @return http response.
     */
    @POST
    @RolesAllowed({ROLE_WRITE})
    @Path("/" + OPERATION_GRANTROLE + "/{role}" )
    @ApiOperation(value= "Grant a permission on a feature", response=Response.class)
    @ApiResponses({
        @ApiResponse(code = 204, message= "Permission has been granted"), 
        @ApiResponse(code = 404, message= "Feature not found"),
        @ApiResponse(code = 400, message= "Invalid RoleName") })
    public Response operationGrantRole(@PathParam("uid") String id, @PathParam("role") String role) {
        if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
        }
        if ("".equals(role)) {
            String errMsg = "Invalid role should not be null nor empty";
            return Response.status(Response.Status.BAD_REQUEST).entity(errMsg).build();
        }
        getFeatureStore().grantRoleOnFeature(id, role);
        return Response.noContent().build();
    }

    /**
     * Convenient method to update partially the feature: Here removing a role
     * 
     * @return http response.
     */
    @POST
    @RolesAllowed({ROLE_WRITE})
    @Path("/" + OPERATION_REMOVEROLE + "/{role}" )
    @ApiOperation(value= "Remove a permission on a feature", response=Response.class)
    @ApiResponses({
        @ApiResponse(code = 204, message= "Permission has been granted"), 
        @ApiResponse(code = 404, message= "Feature not found"),
        @ApiResponse(code = 400, message= "Invalid RoleName") })
    public Response operationRemoveRole(@PathParam("uid") String id, @PathParam("role") String role) {
        if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
        }
        Set < String > permissions = ff4j.getFeatureStore().read(id).getPermissions();
        if (!permissions.contains(role)) {
            String errMsg = "Invalid role should be within " + permissions;
            return Response.status(Response.Status.BAD_REQUEST).entity(errMsg).build();
        }
        getFeatureStore().removeRoleFromFeature(id, role);
        return Response.noContent().build();
    }
    
    /**
     * Convenient method to update partially the feature: Adding to a group
     * 
     * @return http response.
     */
    @POST
    @RolesAllowed({ROLE_WRITE})
    @Path("/" + OPERATION_ADDGROUP  + "/{groupName}" )
    @ApiOperation(value= "Define the group of the feature", response=Response.class)
    @ApiResponses({
        @ApiResponse(code = 204, message= "Group has been defined"), 
        @ApiResponse(code = 404, message= "Feature not found"),
        @ApiResponse(code = 400, message= "Invalid GroupName") })
    public Response operationAddGroup(@PathParam("uid") String id, @PathParam("groupName") String groupName) {
        if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
        }
        if ("".equals(groupName)) {
            String errMsg = "Invalid groupName should not be null nor empty";
            return Response.status(Response.Status.BAD_REQUEST).entity(errMsg).build();
        }
        getFeatureStore().addToGroup(id, groupName);
        return Response.noContent().build();
    }
    
    /**
     * Convenient method to update partially the feature: Removing from a group
     * 
     * @return http response.
     */
    @POST
    @RolesAllowed({ROLE_WRITE})
    @Path("/" + OPERATION_REMOVEGROUP  + "/{groupName}")
    @ApiOperation(value= "Remove the group of the feature", response=Response.class)
    @ApiResponses({
        @ApiResponse(code = 204, message= "Group has been removed"), 
        @ApiResponse(code = 404, message= "Feature not found"),
        @ApiResponse(code = 400, message= "Invalid GroupName") })
    public Response operationRemoveGroup(@PathParam("uid") String id,  @PathParam("groupName") String groupName) {
        if (!ff4j.getFeatureStore().exist(id)) {
            String errMsg = new FeatureNotFoundException(id).getMessage();
            return Response.status(Response.Status.NOT_FOUND).entity(errMsg).build();
        }
        // Expected behaviour is no error even if invalid groupname
        // .. but invalid if group does not exist... 
        if (!ff4j.getFeatureStore().existGroup(groupName)) {
            String errMsg = "Invalid groupName should be " + groupName;
            return Response.status(Response.Status.BAD_REQUEST).entity(errMsg).build();
        }
        getFeatureStore().removeFromGroup(id, groupName);
        return Response.noContent().build();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy