eriee
eriee

Reputation: 396

How to dynamically disable specific API in spring?

I have a flag DISABLE_FLAG and I want to use it to control multiple specific APIs in different controllers.

@RestController
public final class Controller1 {
    @RequestMapping(value = "/foo1", method = RequestMethod.POST)
    public String foo1()
}
@RestController
public final class Controller2 {
    @RequestMapping(value = "/foo2", method = RequestMethod.POST)
    public String foo2()
}

I can use an interceptor to handle all the urls. Is there a easy way to do that like annotation?

Upvotes: 3

Views: 2959

Answers (4)

I did it as follows :

@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface DisableApiControl {
}

This class is my customization statement. After could use AOP :

for AbstractBaseServiceImpl :

public abstract class AbstractBaseServiceImpl {

    private static boolean disableCheck = false;

    public void setDisableChecker(boolean checkParameter) {
        disableCheck = checkParameter;
    }

    public boolean getDisableChecker() {
        return disableCheck;
    }
}

NOTE : The above class has been prepared to provide a dynamic structure.

@Aspect
@Component
public class DisableApiControlAspect extends AbstractBaseServiceImpl {

    @Autowired
    private HttpServletResponse httpServletResponse;

    @Pointcut(" @annotation(disableMe)")
    protected void disabledMethods(DisableApiControl disableMe) {

        // comment line 
        
    }

    @Around("disabledMethods(disableMe)")
    public Object dontRun(ProceedingJoinPoint joinPoint, DisableApiControl disableMe) throws Throwable {
        if (getDisableChecker()) {
            httpServletResponse.sendError(HttpStatus.NOT_FOUND.value(), "Not found");
            return null;
        } else {
            return joinPoint.proceed();
        }
    }
}

checker parameter added global at this point. The rest will be easier when the value is given as true / false when needed.

@GetMapping("/map")
@DisableApiControl
public List<?> stateMachineFindMap() {
    return new ArrayList<>;
}

Upvotes: 0

Jaqueline Schweigert
Jaqueline Schweigert

Reputation: 83

You can solve this with annotations by utilizing spring profiles. You define two profiles one for enabled flag and another profile for the disabled flag. Your example would look like this:

@Profile("DISABLED_FLAG")
@RestController
public final class Controller1 {
    @RequestMapping(value = "/foo1", method = RequestMethod.POST)
    public String foo1()
}

@Profile("ENABLED_FLAG")
@RestController
public final class Controller2 {
    @RequestMapping(value = "/foo2", method = RequestMethod.POST)
    public String foo2()
}

Here is the link to the spring framework documentation for this feature: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html

Upvotes: 1

Izzy
Izzy

Reputation: 238

You could use AOP to do something like that.

Create your own annotation...

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Maybe { }

and corresponding aspect...

@Aspect
public class MaybeAspect {

  @Pointcut("@annotation(com.example.Maybe)")
  public void callMeMaybe() {}

  @Around("callMeMaybe()")
  public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    // do your logic here..
    if(DISABLE_FOO) {
      // do nothing ? throw exception?
      // return null;
      throw new IllegalStateException();
    } else {
      // process the request normally
      return joinPoint.proceed();
    }
  }
}

Upvotes: 2

Pasupathi Rajamanickam
Pasupathi Rajamanickam

Reputation: 2052

I don't think there is direct way to disable a constructed request mapping but We can disable API in many ways with some condition.

Here is the 2 ways disabling by spring profile or JVM properties.

public class SampleController {
    @Autowired
    Environment env;

    @RequestMapping(value = "/foo", method = RequestMethod.POST)
    public String foo(HttpServletResponse response) {
        // Using profile
        if (env.acceptsProfiles("staging")) {
            response.setStatus(404);
            return "";
        }

        // Using JVM options
        if("true".equals(System.getProperty("DISABLE_FOO"))) {
            response.setStatus(404);
            return "";
        }

        return "";
    }
}

If you are thinking futuristic solution using cloud config is the best approach. https://spring.io/guides/gs/centralized-configuration/

Using Conditional components

This allows to build bean with conditions, if the condition failed on startup, the entire component will never be built. Group all your optional request mapping to new controller and add conditional annotation

@Conditional(ConditionalController.class)
public class SampleController {
    @Autowired
    Environment env;

    @RequestMapping(value = "/foo", method = RequestMethod.POST)
    public String foo(HttpServletResponse response) {
        return "";
    }

    public static class ConditionalController implements Condition {

        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return context.getEnvironment().acceptsProfiles("staging"); // Or whatever condition
        }

    }
}

Upvotes: 1

Related Questions