From b17b470db9d42d25f3c41b02d0b16c53c732bfd0 Mon Sep 17 00:00:00 2001 From: XTiNCT Date: Sun, 8 Feb 2026 14:00:35 +0530 Subject: [PATCH] added flag for add new pet --- .../featureflag/annotation/FeatureToggle.java | 61 ++--- .../controller/FeatureFlagController.java | 258 +++++++++--------- .../featureflag/dto/FeatureCheckRequest.java | 28 +- .../featureflag/dto/FeatureCheckResponse.java | 54 ++-- .../featureflag/dto/FeatureFlagRequest.java | 129 ++++----- .../featureflag/dto/FeatureFlagResponse.java | 171 ++++++------ .../service/FeatureFlagService.java | 30 +- .../petclinic/owner/OwnerController.java | 9 +- .../petclinic/owner/PetController.java | 9 +- .../templates/owners/ownerDetails.html | 2 +- 10 files changed, 376 insertions(+), 375 deletions(-) diff --git a/src/main/java/org/springframework/samples/petclinic/featureflag/annotation/FeatureToggle.java b/src/main/java/org/springframework/samples/petclinic/featureflag/annotation/FeatureToggle.java index 405bd8ac7..015066b53 100644 --- a/src/main/java/org/springframework/samples/petclinic/featureflag/annotation/FeatureToggle.java +++ b/src/main/java/org/springframework/samples/petclinic/featureflag/annotation/FeatureToggle.java @@ -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 ""; + } diff --git a/src/main/java/org/springframework/samples/petclinic/featureflag/controller/FeatureFlagController.java b/src/main/java/org/springframework/samples/petclinic/featureflag/controller/FeatureFlagController.java index 14d07b65a..aa1d0a2b1 100644 --- a/src/main/java/org/springframework/samples/petclinic/featureflag/controller/FeatureFlagController.java +++ b/src/main/java/org/springframework/samples/petclinic/featureflag/controller/FeatureFlagController.java @@ -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> getAllFlags() { - List 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 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> getAllFlags() { + List 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 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 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 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 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 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 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 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; - } - } } diff --git a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckRequest.java b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckRequest.java index a3a1f20e2..8bf7bd7d7 100644 --- a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckRequest.java +++ b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckRequest.java @@ -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; + } } diff --git a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckResponse.java b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckResponse.java index 96c00d2e0..de6e86399 100644 --- a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckResponse.java +++ b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureCheckResponse.java @@ -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; - } } diff --git a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagRequest.java b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagRequest.java index 1db284bd9..dc02f6611 100644 --- a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagRequest.java +++ b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagRequest.java @@ -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 whitelist; + private Set whitelist; - private Set blacklist; + private Set 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 getWhitelist() { - return whitelist; - } + public Set getWhitelist() { + return whitelist; + } - public void setWhitelist(Set whitelist) { - this.whitelist = whitelist; - } + public void setWhitelist(Set whitelist) { + this.whitelist = whitelist; + } - public Set getBlacklist() { - return blacklist; - } + public Set getBlacklist() { + return blacklist; + } - public void setBlacklist(Set blacklist) { - this.blacklist = blacklist; - } + public void setBlacklist(Set 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; - } } diff --git a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagResponse.java b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagResponse.java index aa0950e8f..7599431bc 100644 --- a/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagResponse.java +++ b/src/main/java/org/springframework/samples/petclinic/featureflag/dto/FeatureFlagResponse.java @@ -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 whitelist; + private Set whitelist; - private Set blacklist; + private Set 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 getWhitelist() { - return whitelist; - } + public Set getWhitelist() { + return whitelist; + } - public void setWhitelist(Set whitelist) { - this.whitelist = whitelist; - } + public void setWhitelist(Set whitelist) { + this.whitelist = whitelist; + } - public Set getBlacklist() { - return blacklist; - } + public Set getBlacklist() { + return blacklist; + } - public void setBlacklist(Set blacklist) { - this.blacklist = blacklist; - } + public void setBlacklist(Set 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; + } } diff --git a/src/main/java/org/springframework/samples/petclinic/featureflag/service/FeatureFlagService.java b/src/main/java/org/springframework/samples/petclinic/featureflag/service/FeatureFlagService.java index fa98d30b1..319b1a239 100644 --- a/src/main/java/org/springframework/samples/petclinic/featureflag/service/FeatureFlagService.java +++ b/src/main/java/org/springframework/samples/petclinic/featureflag/service/FeatureFlagService.java @@ -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; } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java index 199ca3611..a70851969 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java @@ -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; } diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java index 8398e4f13..2da5efcf3 100644 --- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java +++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java @@ -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); diff --git a/src/main/resources/templates/owners/ownerDetails.html b/src/main/resources/templates/owners/ownerDetails.html index cc175cd13..1f298533f 100644 --- a/src/main/resources/templates/owners/ownerDetails.html +++ b/src/main/resources/templates/owners/ownerDetails.html @@ -35,7 +35,7 @@ Edit Owner - Add + Add New Pet