mirror of
https://github.com/spring-projects/spring-petclinic.git
synced 2026-02-11 09:01:10 +00:00
added flag for add new pet
This commit is contained in:
parent
c518db81a2
commit
b17b470db9
10 changed files with 376 additions and 375 deletions
|
|
@ -7,42 +7,39 @@ import java.lang.annotation.Target;
|
|||
|
||||
/**
|
||||
* Custom annotation to mark methods protected by feature toggle
|
||||
*
|
||||
*
|
||||
* Usage:
|
||||
* @FeatureToggle(key = "add-new-pet")
|
||||
* public String processNewPetForm(...) {
|
||||
* // method implementation
|
||||
* }
|
||||
*
|
||||
*
|
||||
* @FeatureToggle(key = "add-new-pet") public String processNewPetForm(...) { // method
|
||||
* implementation }
|
||||
*
|
||||
* With context extraction:
|
||||
* @FeatureToggle(key = "owner-search", contextParam = "name")
|
||||
* public String processFindForm(@RequestParam("name") String name, ...) {
|
||||
* // method implementation
|
||||
* }
|
||||
* @FeatureToggle(key = "owner-search", contextParam = "name") public String
|
||||
* processFindForm(@RequestParam("name") String name, ...) { // method implementation }
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface FeatureToggle {
|
||||
|
||||
/**
|
||||
* The feature flag key to check
|
||||
*/
|
||||
String key();
|
||||
|
||||
/**
|
||||
* Optional: name of the parameter to use as context
|
||||
* If specified, the value of this parameter will be used for whitelist/blacklist/percentage evaluation
|
||||
*/
|
||||
String contextParam() default "";
|
||||
|
||||
/**
|
||||
* Optional: custom message to show when feature is disabled
|
||||
*/
|
||||
String disabledMessage() default "This feature is currently disabled";
|
||||
|
||||
/**
|
||||
* Optional: redirect path when feature is disabled
|
||||
* If empty, will show error message
|
||||
*/
|
||||
String disabledRedirect() default "";
|
||||
|
||||
/**
|
||||
* The feature flag key to check
|
||||
*/
|
||||
String key();
|
||||
|
||||
/**
|
||||
* Optional: name of the parameter to use as context If specified, the value of this
|
||||
* parameter will be used for whitelist/blacklist/percentage evaluation
|
||||
*/
|
||||
String contextParam() default "";
|
||||
|
||||
/**
|
||||
* Optional: custom message to show when feature is disabled
|
||||
*/
|
||||
String disabledMessage() default "This feature is currently disabled";
|
||||
|
||||
/**
|
||||
* Optional: redirect path when feature is disabled If empty, will show error message
|
||||
*/
|
||||
String disabledRedirect() default "";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,149 +20,145 @@ import org.springframework.samples.petclinic.featureflag.dto.*;
|
|||
@RestController
|
||||
@RequestMapping("/feature-flags")
|
||||
public class FeatureFlagController {
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
public FeatureFlagController(FeatureFlagService featureFlagService) {
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
/**
|
||||
* GET /api/feature-flags
|
||||
* Get all feature flags
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<List<FeatureFlagResponse>> getAllFlags() {
|
||||
List<FeatureFlagResponse> flags = featureFlagService.getAllFlags()
|
||||
.stream()
|
||||
.map(FeatureFlagResponse::fromEntity)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(flags);
|
||||
}
|
||||
public FeatureFlagController(FeatureFlagService featureFlagService) {
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/feature-flags/{id}
|
||||
* Get a specific feature flag by ID
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<FeatureFlagResponse> getFlagById(@PathVariable Long id) {
|
||||
return featureFlagService.getFlagById(id)
|
||||
.map(FeatureFlagResponse::fromEntity)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
/**
|
||||
* GET /api/feature-flags Get all feature flags
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<List<FeatureFlagResponse>> getAllFlags() {
|
||||
List<FeatureFlagResponse> flags = featureFlagService.getAllFlags()
|
||||
.stream()
|
||||
.map(FeatureFlagResponse::fromEntity)
|
||||
.collect(Collectors.toList());
|
||||
return ResponseEntity.ok(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/feature-flags/key/{flagKey}
|
||||
* Get a specific feature flag by key
|
||||
*/
|
||||
@GetMapping("/key/{flagKey}")
|
||||
public ResponseEntity<FeatureFlagResponse> getFlagByKey(@PathVariable String flagKey) {
|
||||
return featureFlagService.getFlagByKey(flagKey)
|
||||
.map(FeatureFlagResponse::fromEntity)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
/**
|
||||
* GET /api/feature-flags/{id} Get a specific feature flag by ID
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<FeatureFlagResponse> getFlagById(@PathVariable Long id) {
|
||||
return featureFlagService.getFlagById(id)
|
||||
.map(FeatureFlagResponse::fromEntity)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/feature-flags
|
||||
* Create a new feature flag
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<?> createFlag(@RequestBody FeatureFlagRequest request) {
|
||||
try {
|
||||
FeatureFlag flag = request.toEntity();
|
||||
FeatureFlag created = featureFlagService.createFlag(flag);
|
||||
return ResponseEntity.status(HttpStatus.CREATED)
|
||||
.body(FeatureFlagResponse.fromEntity(created));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* GET /api/feature-flags/key/{flagKey} Get a specific feature flag by key
|
||||
*/
|
||||
@GetMapping("/key/{flagKey}")
|
||||
public ResponseEntity<FeatureFlagResponse> getFlagByKey(@PathVariable String flagKey) {
|
||||
return featureFlagService.getFlagByKey(flagKey)
|
||||
.map(FeatureFlagResponse::fromEntity)
|
||||
.map(ResponseEntity::ok)
|
||||
.orElse(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/feature-flags/{id}
|
||||
* Update an existing feature flag
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<?> updateFlag(@PathVariable Long id, @RequestBody FeatureFlagRequest request) {
|
||||
try {
|
||||
FeatureFlag flag = request.toEntity();
|
||||
FeatureFlag updated = featureFlagService.updateFlag(id, flag);
|
||||
return ResponseEntity.ok(FeatureFlagResponse.fromEntity(updated));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* POST /api/feature-flags Create a new feature flag
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<?> createFlag(@RequestBody FeatureFlagRequest request) {
|
||||
try {
|
||||
FeatureFlag flag = request.toEntity();
|
||||
FeatureFlag created = featureFlagService.createFlag(flag);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(FeatureFlagResponse.fromEntity(created));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/feature-flags/{id}
|
||||
* Delete a feature flag
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<?> deleteFlag(@PathVariable Long id) {
|
||||
try {
|
||||
featureFlagService.deleteFlag(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* PUT /api/feature-flags/{id} Update an existing feature flag
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<?> updateFlag(@PathVariable Long id, @RequestBody FeatureFlagRequest request) {
|
||||
try {
|
||||
FeatureFlag flag = request.toEntity();
|
||||
FeatureFlag updated = featureFlagService.updateFlag(id, flag);
|
||||
return ResponseEntity.ok(FeatureFlagResponse.fromEntity(updated));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/feature-flags/{flagKey}/toggle
|
||||
* Toggle a feature flag on/off
|
||||
*/
|
||||
@PostMapping("/{flagKey}/toggle")
|
||||
public ResponseEntity<?> toggleFlag(@PathVariable String flagKey) {
|
||||
try {
|
||||
FeatureFlag toggled = featureFlagService.toggleFlag(flagKey);
|
||||
return ResponseEntity.ok(FeatureFlagResponse.fromEntity(toggled));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* DELETE /api/feature-flags/{id} Delete a feature flag
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<?> deleteFlag(@PathVariable Long id) {
|
||||
try {
|
||||
featureFlagService.deleteFlag(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/feature-flags/check
|
||||
* Check if a feature is enabled for a given context
|
||||
*/
|
||||
@PostMapping("/check")
|
||||
public ResponseEntity<FeatureCheckResponse> checkFeature(@RequestBody FeatureCheckRequest request) {
|
||||
boolean enabled = featureFlagService.isFeatureEnabled(request.getFlagKey(), request.getContext());
|
||||
FeatureCheckResponse response = new FeatureCheckResponse(
|
||||
request.getFlagKey(),
|
||||
enabled,
|
||||
request.getContext());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
/**
|
||||
* POST /api/feature-flags/{flagKey}/toggle Toggle a feature flag on/off
|
||||
*/
|
||||
@PostMapping("/{flagKey}/toggle")
|
||||
public ResponseEntity<?> toggleFlag(@PathVariable String flagKey) {
|
||||
try {
|
||||
FeatureFlag toggled = featureFlagService.toggleFlag(flagKey);
|
||||
return ResponseEntity.ok(FeatureFlagResponse.fromEntity(toggled));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/feature-flags/check/{flagKey}
|
||||
* Check if a feature is enabled (simple check without context)
|
||||
*/
|
||||
@GetMapping("/check/{flagKey}")
|
||||
public ResponseEntity<FeatureCheckResponse> checkFeatureSimple(@PathVariable String flagKey) {
|
||||
boolean enabled = featureFlagService.isFeatureEnabled(flagKey);
|
||||
FeatureCheckResponse response = new FeatureCheckResponse(flagKey, enabled, null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
/**
|
||||
* POST /api/feature-flags/check Check if a feature is enabled for a given context
|
||||
*/
|
||||
@PostMapping("/check")
|
||||
public ResponseEntity<FeatureCheckResponse> checkFeature(@RequestBody FeatureCheckRequest request) {
|
||||
boolean enabled = featureFlagService.isFeatureEnabled(request.getFlagKey(), request.getContext());
|
||||
FeatureCheckResponse response = new FeatureCheckResponse(request.getFlagKey(), enabled, request.getContext());
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error response class
|
||||
*/
|
||||
public static class ErrorResponse {
|
||||
private String error;
|
||||
/**
|
||||
* GET /api/feature-flags/check/{flagKey} Check if a feature is enabled (simple check
|
||||
* without context)
|
||||
*/
|
||||
@GetMapping("/check/{flagKey}")
|
||||
public ResponseEntity<FeatureCheckResponse> checkFeatureSimple(@PathVariable String flagKey) {
|
||||
boolean enabled = featureFlagService.isFeatureEnabled(flagKey);
|
||||
FeatureCheckResponse response = new FeatureCheckResponse(flagKey, enabled, null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
/**
|
||||
* Error response class
|
||||
*/
|
||||
public static class ErrorResponse {
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
private String error;
|
||||
|
||||
public ErrorResponse(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,24 +2,24 @@ package org.springframework.samples.petclinic.featureflag.dto;
|
|||
|
||||
public class FeatureCheckRequest {
|
||||
|
||||
private String flagKey;
|
||||
private String flagKey;
|
||||
|
||||
private String context;
|
||||
private String context;
|
||||
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
return context;
|
||||
}
|
||||
public String getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void setContext(String context) {
|
||||
this.context = context;
|
||||
}
|
||||
public void setContext(String context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,41 @@
|
|||
package org.springframework.samples.petclinic.featureflag.dto;
|
||||
|
||||
public class FeatureCheckResponse {
|
||||
private String flagKey;
|
||||
|
||||
private Boolean enabled;
|
||||
private String flagKey;
|
||||
|
||||
private String context;
|
||||
private Boolean enabled;
|
||||
|
||||
public FeatureCheckResponse(String flagKey, Boolean enabled, String context) {
|
||||
this.flagKey = flagKey;
|
||||
this.enabled = enabled;
|
||||
this.context = context;
|
||||
}
|
||||
private String context;
|
||||
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
public FeatureCheckResponse(String flagKey, Boolean enabled, String context) {
|
||||
this.flagKey = flagKey;
|
||||
this.enabled = enabled;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
return context;
|
||||
}
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void setContext(String context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void setContext(String context) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,89 +7,90 @@ import org.springframework.samples.petclinic.featureflag.entity.FlagType;
|
|||
|
||||
public class FeatureFlagRequest {
|
||||
|
||||
private String flagKey;
|
||||
private String flagKey;
|
||||
|
||||
private String description;
|
||||
private String description;
|
||||
|
||||
private FlagType flagType;
|
||||
private FlagType flagType;
|
||||
|
||||
private Boolean enabled;
|
||||
private Boolean enabled;
|
||||
|
||||
private Integer percentage;
|
||||
private Integer percentage;
|
||||
|
||||
private Set<String> whitelist;
|
||||
private Set<String> whitelist;
|
||||
|
||||
private Set<String> blacklist;
|
||||
private Set<String> blacklist;
|
||||
|
||||
// Getters and Setters
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
// Getters and Setters
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public FlagType getFlagType() {
|
||||
return flagType;
|
||||
}
|
||||
public FlagType getFlagType() {
|
||||
return flagType;
|
||||
}
|
||||
|
||||
public void setFlagType(FlagType flagType) {
|
||||
this.flagType = flagType;
|
||||
}
|
||||
public void setFlagType(FlagType flagType) {
|
||||
this.flagType = flagType;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Integer getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
public Integer getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
|
||||
public void setPercentage(Integer percentage) {
|
||||
this.percentage = percentage;
|
||||
}
|
||||
public void setPercentage(Integer percentage) {
|
||||
this.percentage = percentage;
|
||||
}
|
||||
|
||||
public Set<String> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
public Set<String> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
public void setWhitelist(Set<String> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
public void setWhitelist(Set<String> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public Set<String> getBlacklist() {
|
||||
return blacklist;
|
||||
}
|
||||
public Set<String> getBlacklist() {
|
||||
return blacklist;
|
||||
}
|
||||
|
||||
public void setBlacklist(Set<String> blacklist) {
|
||||
this.blacklist = blacklist;
|
||||
}
|
||||
public void setBlacklist(Set<String> blacklist) {
|
||||
this.blacklist = blacklist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert DTO to Entity
|
||||
*/
|
||||
public FeatureFlag toEntity() {
|
||||
FeatureFlag flag = new FeatureFlag();
|
||||
flag.setFlagKey(this.flagKey);
|
||||
flag.setDescription(this.description);
|
||||
flag.setFlagType(this.flagType != null ? this.flagType : FlagType.SIMPLE);
|
||||
flag.setEnabled(this.enabled != null ? this.enabled : false);
|
||||
flag.setPercentage(this.percentage);
|
||||
flag.setWhitelist(this.whitelist != null ? this.whitelist : Set.of());
|
||||
flag.setBlacklist(this.blacklist != null ? this.blacklist : Set.of());
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert DTO to Entity
|
||||
*/
|
||||
public FeatureFlag toEntity() {
|
||||
FeatureFlag flag = new FeatureFlag();
|
||||
flag.setFlagKey(this.flagKey);
|
||||
flag.setDescription(this.description);
|
||||
flag.setFlagType(this.flagType != null ? this.flagType : FlagType.SIMPLE);
|
||||
flag.setEnabled(this.enabled != null ? this.enabled : false);
|
||||
flag.setPercentage(this.percentage);
|
||||
flag.setWhitelist(this.whitelist != null ? this.whitelist : Set.of());
|
||||
flag.setBlacklist(this.blacklist != null ? this.blacklist : Set.of());
|
||||
return flag;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,121 +7,120 @@ import org.springframework.samples.petclinic.featureflag.entity.FlagType;
|
|||
|
||||
public class FeatureFlagResponse {
|
||||
|
||||
private Long id;
|
||||
private Long id;
|
||||
|
||||
private String flagKey;
|
||||
private String flagKey;
|
||||
|
||||
private String description;
|
||||
private String description;
|
||||
|
||||
private FlagType flagType;
|
||||
private FlagType flagType;
|
||||
|
||||
private Boolean enabled;
|
||||
private Boolean enabled;
|
||||
|
||||
private Integer percentage;
|
||||
private Integer percentage;
|
||||
|
||||
private Set<String> whitelist;
|
||||
private Set<String> whitelist;
|
||||
|
||||
private Set<String> blacklist;
|
||||
private Set<String> blacklist;
|
||||
|
||||
private String createdAt;
|
||||
private String createdAt;
|
||||
|
||||
private String updatedAt;
|
||||
private String updatedAt;
|
||||
|
||||
public static FeatureFlagResponse fromEntity(FeatureFlag flag) {
|
||||
FeatureFlagResponse response = new FeatureFlagResponse();
|
||||
response.setId(flag.getId());
|
||||
response.setFlagKey(flag.getFlagKey());
|
||||
response.setDescription(flag.getDescription());
|
||||
response.setFlagType(flag.getFlagType());
|
||||
response.setEnabled(flag.isEnabled());
|
||||
response.setPercentage(flag.getPercentage());
|
||||
response.setWhitelist(flag.getWhitelist());
|
||||
response.setBlacklist(flag.getBlacklist());
|
||||
response.setCreatedAt(flag.getCreatedAt().toString());
|
||||
response.setUpdatedAt(flag.getUpdatedAt().toString());
|
||||
return response;
|
||||
}
|
||||
public static FeatureFlagResponse fromEntity(FeatureFlag flag) {
|
||||
FeatureFlagResponse response = new FeatureFlagResponse();
|
||||
response.setId(flag.getId());
|
||||
response.setFlagKey(flag.getFlagKey());
|
||||
response.setDescription(flag.getDescription());
|
||||
response.setFlagType(flag.getFlagType());
|
||||
response.setEnabled(flag.isEnabled());
|
||||
response.setPercentage(flag.getPercentage());
|
||||
response.setWhitelist(flag.getWhitelist());
|
||||
response.setBlacklist(flag.getBlacklist());
|
||||
response.setCreatedAt(flag.getCreatedAt().toString());
|
||||
response.setUpdatedAt(flag.getUpdatedAt().toString());
|
||||
return response;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
// Getters and Setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
public String getFlagKey() {
|
||||
return flagKey;
|
||||
}
|
||||
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
public void setFlagKey(String flagKey) {
|
||||
this.flagKey = flagKey;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public FlagType getFlagType() {
|
||||
return flagType;
|
||||
}
|
||||
public FlagType getFlagType() {
|
||||
return flagType;
|
||||
}
|
||||
|
||||
public void setFlagType(FlagType flagType) {
|
||||
this.flagType = flagType;
|
||||
}
|
||||
public void setFlagType(FlagType flagType) {
|
||||
this.flagType = flagType;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Integer getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
public Integer getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
|
||||
public void setPercentage(Integer percentage) {
|
||||
this.percentage = percentage;
|
||||
}
|
||||
public void setPercentage(Integer percentage) {
|
||||
this.percentage = percentage;
|
||||
}
|
||||
|
||||
public Set<String> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
public Set<String> getWhitelist() {
|
||||
return whitelist;
|
||||
}
|
||||
|
||||
public void setWhitelist(Set<String> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
public void setWhitelist(Set<String> whitelist) {
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public Set<String> getBlacklist() {
|
||||
return blacklist;
|
||||
}
|
||||
public Set<String> getBlacklist() {
|
||||
return blacklist;
|
||||
}
|
||||
|
||||
public void setBlacklist(Set<String> blacklist) {
|
||||
this.blacklist = blacklist;
|
||||
}
|
||||
public void setBlacklist(Set<String> blacklist) {
|
||||
this.blacklist = blacklist;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ import org.springframework.samples.petclinic.featureflag.entity.FlagType;
|
|||
import org.springframework.samples.petclinic.featureflag.repository.FeatureFlagRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Feature Flag Service - Core service for managing and evaluating feature flags
|
||||
*
|
||||
* This service provides:
|
||||
* - CRUD operations for feature flags
|
||||
* - Advanced flag evaluation with multiple strategies
|
||||
* - Helper methods for easy integration anywhere in the application
|
||||
*
|
||||
* This service provides: - CRUD operations for feature flags - Advanced flag evaluation
|
||||
* with multiple strategies - Helper methods for easy integration anywhere in the
|
||||
* application
|
||||
*/
|
||||
@Service
|
||||
@Transactional
|
||||
|
|
@ -31,9 +31,8 @@ public class FeatureFlagService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Main helper function to check if a feature is enabled
|
||||
* Can be called from anywhere in the application
|
||||
*
|
||||
* Main helper function to check if a feature is enabled Can be called from anywhere
|
||||
* in the application
|
||||
* @param flagKey The unique identifier for the feature flag
|
||||
* @return true if feature is enabled, false otherwise
|
||||
*/
|
||||
|
|
@ -43,7 +42,6 @@ public class FeatureFlagService {
|
|||
|
||||
/**
|
||||
* Check if feature is enabled for a specific context/user
|
||||
*
|
||||
* @param flagKey The unique identifier for the feature flag
|
||||
* @param context Context identifier (e.g., userId, sessionId, email)
|
||||
* @return true if feature is enabled for this context, false otherwise
|
||||
|
|
@ -69,12 +67,10 @@ public class FeatureFlagService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Evaluate a feature flag based on its type and configuration
|
||||
* Evaluation order (highest to lowest priority):
|
||||
* 1. GLOBAL_DISABLE - always returns false
|
||||
* 2. Blacklist - if context is blacklisted, return false
|
||||
* 3. Enabled check - if not enabled, return false
|
||||
* 4. Type-specific evaluation (WHITELIST, PERCENTAGE, SIMPLE)
|
||||
* Evaluate a feature flag based on its type and configuration Evaluation order
|
||||
* (highest to lowest priority): 1. GLOBAL_DISABLE - always returns false 2. Blacklist
|
||||
* - if context is blacklisted, return false 3. Enabled check - if not enabled, return
|
||||
* false 4. Type-specific evaluation (WHITELIST, PERCENTAGE, SIMPLE)
|
||||
*/
|
||||
private boolean evaluateFlag(FeatureFlag flag, String context) {
|
||||
// GLOBAL_DISABLE always returns false
|
||||
|
|
@ -154,8 +150,8 @@ public class FeatureFlagService {
|
|||
int bucket = hash % 100;
|
||||
|
||||
boolean enabled = bucket < flag.getPercentage();
|
||||
logger.debug("Percentage flag '{}' for context '{}': bucket={}, percentage={}, enabled={}",
|
||||
flag.getFlagKey(), context, bucket, flag.getPercentage(), enabled);
|
||||
logger.debug("Percentage flag '{}' for context '{}': bucket={}, percentage={}, enabled={}", flag.getFlagKey(),
|
||||
context, bucket, flag.getPercentage(), enabled);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import java.util.Optional;
|
|||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.samples.petclinic.featureflag.service.FeatureFlagService;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
|
|
@ -51,9 +52,11 @@ class OwnerController {
|
|||
private static final String VIEWS_OWNER_CREATE_OR_UPDATE_FORM = "owners/createOrUpdateOwnerForm";
|
||||
|
||||
private final OwnerRepository owners;
|
||||
private final FeatureFlagService featureFlagService;
|
||||
|
||||
public OwnerController(OwnerRepository owners) {
|
||||
public OwnerController(OwnerRepository owners, FeatureFlagService featureFlagService) {
|
||||
this.owners = owners;
|
||||
this.featureFlagService = featureFlagService;
|
||||
}
|
||||
|
||||
@InitBinder
|
||||
|
|
@ -170,6 +173,10 @@ class OwnerController {
|
|||
Owner owner = optionalOwner.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Owner not found with id: " + ownerId + ". Please ensure the ID is correct "));
|
||||
mav.addObject(owner);
|
||||
|
||||
// displaying add pet button based on feature toggle
|
||||
boolean addNewPetEnabled = featureFlagService.isFeatureEnabled("ADD_NEW_PET","addNewPetEnabled");
|
||||
mav.addObject("addNewPetEnabled", addNewPetEnabled);
|
||||
return mav;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.util.Collection;
|
|||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.samples.petclinic.featureflag.annotation.FeatureToggle;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.ModelMap;
|
||||
import org.springframework.util.Assert;
|
||||
|
|
@ -95,6 +96,7 @@ class PetController {
|
|||
dataBinder.setValidator(new PetValidator());
|
||||
}
|
||||
|
||||
@FeatureToggle(key = "ADD_NEW_PET", disabledMessage = "Adding new pets is currently disabled", disabledRedirect = "/owners/{ownerId}")
|
||||
@GetMapping("/pets/new")
|
||||
public String initCreationForm(Owner owner, ModelMap model) {
|
||||
Pet pet = new Pet();
|
||||
|
|
@ -102,6 +104,7 @@ class PetController {
|
|||
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
|
||||
}
|
||||
|
||||
@FeatureToggle(key = "ADD_NEW_PET", disabledMessage = "Adding new pets is currently disabled", disabledRedirect = "/owners/{ownerId}")
|
||||
@PostMapping("/pets/new")
|
||||
public String processCreationForm(Owner owner, @Valid Pet pet, BindingResult result,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
|
|
@ -159,8 +162,9 @@ class PetController {
|
|||
|
||||
/**
|
||||
* Updates the pet details if it exists or adds a new pet to the owner.
|
||||
*
|
||||
* @param owner The owner of the pet
|
||||
* @param pet The pet with updated details
|
||||
* @param pet The pet with updated details
|
||||
*/
|
||||
private void updatePetDetails(Owner owner, Pet pet) {
|
||||
Integer id = pet.getId();
|
||||
|
|
@ -171,8 +175,7 @@ class PetController {
|
|||
existingPet.setName(pet.getName());
|
||||
existingPet.setBirthDate(pet.getBirthDate());
|
||||
existingPet.setType(pet.getType());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
owner.addPet(pet);
|
||||
}
|
||||
this.owners.save(owner);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
<a th:href="@{__${owner.id}__/edit}" class="btn btn-primary" th:text="#{editOwner}">Edit
|
||||
Owner</a>
|
||||
<a th:href="@{__${owner.id}__/pets/new}" class="btn btn-primary" th:text="#{addNewPet}">Add
|
||||
<a th:if="${addNewPetEnabled}" th:href="@{__${owner.id}__/pets/new}" class="btn btn-primary" th:text="#{addNewPet}">Add
|
||||
New Pet</a>
|
||||
|
||||
<br />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue