How to maintain versions of RESTful API in Java? - java

I'd like to implement versioning in my RESTful web service API. I intend to put the version into the URL, viz.: /api/v3/endpoint
What is the ideal way to do this (in Java)?
Although this irritates my aversion to manual version control, my best guess is to save the API interface into a new file and include a bunch of comments to defend against too much entropy:
/** Do not leave more than 2 previous versions in existence! **/
#Path("/api/v3")
public interface RestfulAPIv3
{
int version = 3;
#Path("/resources")
#Method(GET)
public Response getResources();
}
Of course the idea would be not to copy the implementation also, but to allow it to support multiple versions. This might require moving identical signatures forward to the newer versions so no collisions would happen across interfaces in the class file:
public class RestfulAPIImpl implements RestfulAPIv3, RestfulAPIv2
{
public Response getResources()
{
List<Resource> rs = HibernateHelper.getAll(Resource.class);
// Can we do something with v2/v3 diffs here?
}
#Deprecated
public Response getOptions() // ONLY in v2!
{
return HibernateHelper.getOptions();
}
}
Thinking it through, I have no idea how we'd know which version of an endpoint the client has called, except maybe forwarding the request into the methods which is not my favorite thing.
So, my question is - what have all the versioned API implementers been doing to keep all this stuff from getting out of hand? What's the best way to do this? Am I on the right track?
(Note: this other question is about the 'if' - my question is about the 'how'.)

An alternative to not passing forward a parameter specifying the version number is to add an annotation to the method so that it automatically captures that information and saves it on a request object that can be read elsewhere.
Taking into account that your API might have requests with parameters that differ amongst versions and also have responses that look different you might have to have multiple controllers and view-model classes, one for each version of the API.
UPDATE
As per request, follows some sample code (I've used Play Framework 2.4).
So the objective is to achieve something like this in a controller class:
#Versioned(version = 0.1)
public Result postUsers() {
// get post data
UsersService service = new UsersService(getContext());
service.postUsers(postData);
// return result
}
And like this in a service class:
public class UsersService extends Service {
public UsersService(RequestContext context) {
super(context);
}
public ReturnType postUsers() {
double apiVersion = getContext().getAPIVersion();
// business logic
}
}
In order to accomplish that, you would have a Versioned annotation:
import java.lang.annotation.*;
import play.mvc.With;
#With(VersioningController.class)
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Versioned {
double version();
}
And a VersioningController:
import play.libs.F.Promise;
import play.mvc.*;
public class VersioningController extends Action<Versioned> {
public final static String API_VERSION = "version";
#Override
public Promise<Result> call(Http.Context context) throws Throwable {
context.args.put(API_VERSION, configuration.version());
return delegate.call(context);
}
}
And a RequestContext to help you manage that (you could also use the request context to manage the request timestamp, the user requesting the operation, etc):
public class RequestContext {
private double version;
public RequestContext(Double version) {
setAPIVersion(version);
}
public double getAPIVersion() {
return version;
}
public void setAPIVersion(double version) {
this.version = version;
}
}
Moreover, your controllers could have a GenericController from which they all extend:
import play.api.Play;
import play.mvc.*;
import play.mvc.Http.Request;
public abstract class GenericController extends Controller {
protected static RequestContext getContext() {
return new RequestContext(getAPIVersion());
}
protected static double getAPIVersion() {
return (double) Http.Context.current().args
.get(VersioningController.API_VERSION);
}
}
And an abstract Service from which all service classes extend:
public abstract class Service {
private RequestContext context;
public Service(RequestContext context) {
setContext(context);
}
public RequestContext getContext() {
return context;
}
public void setContext(RequestContext context) {
this.context = context;
}
}
Having all that said, keep in mind that it could be better to try to isolate the API versioning in as few layers as possible. If you can keep your business logic classes from having to manage API versions it's all the better.

Related

Managing endpoint security and testing approaches

I am working on a small project that includes signing up an account, logging a user in, and request data via certain endpoints. The authentication piece is done via Spring security session Ids. The endpoints consist of publicly available endpoints (i.e. signing up, or forgot password), and some endpoints that require the user to be signed in (i.e. change password, get some content, etc). Kind of like this:
#RestController
public class FightController {
//publicly available
#GetMapping("/public/foo")
String methodForEveryone() {
return "Hi common dude";
}
#GetMapping("secret/bar")
String methodForSpecialPeople() {
return "What happens in fight controller...";
}
}
Spring security then has a list of public endpoints in a static WHITE_LIST
private static final String[] AUTH_WHITELIST = {
//public endpoints
"/public/foo", "swagger", "etc"
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(customAuthFilter(), RequestHeaderAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers("/**").authenticated()
.and()
Tests are currently being done by hitting every endpoint and determining whether it is behaving as expected (via custom antmatchers in the WebSecurityConfigurer). Like so:
package com.fight.testpackages
public class EndpointList {
public static class PublicEndpoints implements ArgumentsProvider {
#Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of("/public/foo"),
);
}
}
public static class PrivateEndoints implements ArgumentsProvider {
#Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of("secret/bar"),
);
}
}
and then managed with tests like
#ParameterizedTest
#ArgumentsSource(PrivateContentEndpoints.class)
public void privateEndpoint_unauthorizedUser_isUnauthorizedResponse(String url) throws Exception {
assertFalse(super.isAuthenticated(url));
}
#WithMockUser(roles = "USER")
#ParameterizedTest
#ArgumentsSource(PublicAccountManagementEndpoints.class)
public void publicEndpoint_authorizedUser_hasAccess(String url) throws Exception {
assertTrue(super.isAuthenticated(url));
}
The issue I am trying to solve can best be described with the following scenario:
A developer adds a new endpoint;
They add the endpoint to list of antmatchers (if it should be public);
And then they add the endpoint to a list of public and private endpoints that gets pulled into the tests.
The problem here is that there is no enforcement of this behaviour, and it's super easy to forget to add an endpoint to a test, or if the names of the endpoints change then the tests need to be updated.
The current setup works, but I was wondering if there was a standard for this? I've looked at the #PreAuthorize and #RolesAllowed, etc but they only seem to be useful for securing a method, and not making it public. I actually want the reverse (i.e. the endpoint to be private by default, and then to be marked as publicly available intentionally).
A solution that I've come up with is as follows:
Create an annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EndpointSecurity {
boolean isPublic() default false;
}
assign annotation to method if you want to make it public
#EndpointSecurity(isPublic = true)
#GetMapping("/public/foo")
String methodForEveryone() {
return "Hi common dude";
}
Build a scanner that checks all RestController methods for the EndpointSecurity annotation and the REST mapping annotation, kind of like below.. Hopefully it's enough to get the point..
#DependsOn("classScanner")
public class ClassMethodScanner {
private final List<Class<? extends Annotation>> annotationFilters;
private List<Method> annotatedMethods;
private final AnnotationScanner<?> classScanner;
public <T extends Annotation> ClassMethodScanner(ClassScanner<T> classScanner) {
this(classScanner, Collections.emptyList());
}
public <T extends Annotation> ClassMethodScanner(ClassScanner<T> classScanner,
List<Class<? extends Annotation>> annotations) {
this.classScanner = classScanner;
this.annotationFilters = annotations;
}
#PostConstruct
void extractAnnotatedMethods() throws ClassNotFoundException {
if (annotatedMethods == null) {
annotatedMethods =
classScanner.getAnnotatedHandlers().stream()
.sequential()
.map(Class::getDeclaredMethods)
.flatMap(Arrays::stream)
.filter(this::hasExpectedAnnotations)
.collect(Collectors.toUnmodifiableList());
}
}
private boolean hasExpectedAnnotations(Method method) {
return
(annotationFilters.isEmpty() && method.getAnnotations().length > 0)
|| annotationFilters.stream().anyMatch(method::isAnnotationPresent);
}
//Is there a good way of making this protected?
public List<Method> getAnnotatedMethods() {
return annotatedMethods;
}
}
And finally produce a list of public and private endpoints that feeds into the HttpSecurity.
public class SecurityEndpoints {
private List<String> publicEndpoints;
private List<String> privateEndpoints;
private final EndpointDetailsCollector<?> collector;
public String[] getWhiteList() {
And feeds into the EndpointList that I mentioned above.
This seems somewhat convoluted though. So was wondering what is a standard approach, or am I making too much of testing the endpoints??

Calling Spring controller method without going to internet

tldr: Is there a way to make an internal request (using the method's path) without going to the internet?
--
Why do I need it? I have a project which receives many events. The decision of who will handle each event is made by a Controller. So I have something similar to this:
#RestController
#RequestMapping("/events")
public class EventHandlerAPI {
#Autowired
private EventAHandler eventAhandler;
#Autowired
private EventBHandler eventBhandler;
#PostMapping("/a")
public void handleEventA(#RequestBody EventA event) {
eventAhandler.handle(id, event);
}
#PostMapping("/b")
public void handleEventB(#RequestBody EventB event) {
eventBhandler.handle(id, event);
}
}
We recently added support to receive events through a Queue service. It sends to us the payload and the event class. Our decision is to let both interfaces working (rest and queue). The solution to avoid code duplication was to keep the Controller choosing which handler will take care of the event. The code nowadays is similar to this:
#Configuration
public class EventHandlerQueueConsumer {
#Autowired
private EventHandlerAPI eventHandlerAPI;
private Map<Class, EventHandler> eventHandlers;
#PostConstruct
public void init() {
/* start listen queue */
declareEventHandlers();
}
private void declareEventHandlers() {
eventHandlers = new HashMap<>();
eventHandlers.put(EventAHandler.class, (EventHandler<EventAHandler>) eventHandlerAPI::handleEventA);
eventHandlers.put(EventBHandler.class, (EventHandler<EventBHandler>) eventHandlerAPI::handleEventB);
}
private void onEventReceived(AbstractEvent event) {
EventHandler eventHandler = eventHandlers.get(event.getClass());
eventHandler.handle(event);
}
private interface EventHandler<T extends AbstractEvent> {
void handle(T event);
}
}
This code works, but it doesn't let the controller choose who will handle the event (our intention). The decision is actually being made by the map.
What I would like to do was to invoke the controller method through it's request mapping without going to the internet. Something like this:
#Configuration
public class EventHandlerQueueConsumer {
// MADE UP CLASS TO SHOW WHAT I WANT
#Autowired
private ControllerInkover controllerInvoker;
#PostConstruct
public void init() { /* start listen queue */ }
private void onEventReceived(AbstractEvent event) {
controllerInvoker.post(event.getPath(), new Object[] { event });
}
}
This way is much cleaner and let all the decisions be made by the controller.
I've researched a lot and didn't found a way to implement it. Debugging spring, I found how he routes the request after the DispatcherServlet, but all the spring internals uses HttpServletRequest and HttpServletResponse :(
Is there a way to make an internal request (using the method's path) without going to the internet?
They are classes of the same application
Then it should easy enough.
1) You can call your own API on http(s)://localhost:{port}/api/{path} using RestTemplate utility class. This is preferred way, since you'll follow standard MVC pattern. Something like:
restTemplate.exchange(uri, HttpMethod.POST, httpEntity, ResponseClass.class);
2) If you don't want to invoke network connection at all, then you can either use Spring's internal to find the mapping/method map or use some reflection to build custom
map upon controller's startup. Then you can pass your event/object to the method from the map in a way shown in your mock-up class. Something like:
#RequestMapping("foo")
public void fooMethod() {
System.out.println("mapping = " + getMapping("fooMethod")); // you can get all methods/mapping in #PostContruct initialization phase
}
private String getMapping(String methodName) {
Method methods[] = this.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName() == methodName) {
String mapping[] = methods[i].getAnnotation(RequestMapping.class).value();
if (mapping.length > 0) {
return mapping[mapping.length - 1];
}
}
}
return null;
}

How to properly access application state from JAX-RS

#Path("/test")
public class MyClass {
#GET
public Response response() {
// Generating some expensive object here.
}
Right now I load the data into arrays etc inside the "response" function, but I want to do it before the query is even made. This way, I want to avoid reloading the data every time a a query is made. How do I achieve this?
This depends on your framework. Are you using the reference implementation Jersey? Jersey comes bundled with HK2 automatically. Then you could add
#Path("/test")
public class MyClass {
#Inject
private MyState stateful;
// here comes your code...
}
to your resource. Of course, you would still need to configure what to inject into this field. With HK2, you use an AbstractBinder for this purpose:
class MyBinder extends AbstractBinder {
private final MyState stateful;
public MyBinder (MyState stateful) {
this.stateful = stateful;
}
#Override
protected void configure() {
bind(stateful).to(MyState.class);
}
}
Finally, you need to add this binder on the application's setup. For this purpose, JAX-RS Application object can be queried for singletons. Simply add the required instance to the application such that it is returned by Application#getSingletons as here:
class MyJaxRSApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
return Collections.singletonSet(MyClass.class);
}
#Override
public Set<Object> getSingletons() {
return Collections.singletonSet(new MyBinder(new MyStateImpl()));
}
}
You can now run your application where MyStateImpl is always injected into MyClass.

RPC call - static methods is not working

I am trying to define a static method in the service interface to make an rpc call. But it doesn't allow me to do so. here I am pasting my code
Client class
public void sendDomesticData(String product,String dma,String yrmnths,String dist,String metrics) {
String url = GWT.getModuleBaseURL() + "domesticservice";
domesticServiceAsync = (DomesticServiceAsync) GWT.create(DomesticService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) domesticServiceAsync;
endpoint.setServiceEntryPoint(url);
domesticServiceAsync.sendDomesticData(product,dma,yrmnths,dist,metrics,new Domestichandler<Void>() );
}
public class Domestichandler<Void> implements AsyncCallback<Void> {
#Override
public void onFailure(Throwable caught) {
String error = caught.getMessage();
System.out.println(error);
}
public void onSuccess(Void result) {
System.out.println("perfect");
}
}
Service
public interface DomesticService extends RemoteService {
public void sendDomesticData(String product,String dma,String yrmnths,String dist,String metrics);
}
public interface DomesticServiceAsync {
void sendDomesticData(String product,String dma,String yrmnths,String dist,String metrics,AsyncCallback<Void> callback);
}
Server side -
public void sendDomesticData(String product, String dma, String yrmnths, String dist, String metrics) {
System.out.println(product);
}
Basically I am trying to send the values from the front interface to the server side and I don't want any return value. But the values passed to the server side should be stored globally in the server class so i can access those values in different method. I tried changing all the senddomestic values to static but it won't allow me to do so? why?
Because RemoteServiceServlet needs to invoke your service methods somehow and the implementation expects instance methods. But this shouldn't prevent you from assigning the method data to static fields. Just be aware of multi threading.
GWT always uses instance methods for RPC calls, static methods are not possible in this case.
What is important to understand about GWT is that any RemoteServiceServlet instances are created and maintained by the servlet container (e.g. Tomcat). The servlet container might create a number of servlet instances on startup (Tomcat creates 6 RemoteServiceServlet instances by default) and then uses load balancing to determine which servlet handles an RPC request at a particular point in time. Depending on settings of course, you have little control over which RemoteServiceServlet instance exactly will handle a specific RPC request.
Therefore, if you want to store information on the server side globally using RPC calls, the idea proposed by YuPPie to use static fields of your RemoteServiceServlet implementation is a BAD idea. You will have no idea which of the RemoteServiceServlet instances maintained by the server contains your static data, and any subsequent calls to retrieve the data will give erratic results.
You have a few options, though. Storing the information in a database (or something similar) is the most straightforward option, but from your post I'm guessing you want something simpler. A singleton class which holds your data is probably the way to go. A thread-safe example:
public class DataContainer
{
private static DataContainer _singleton;
private String _dataField1;
public static synchronized DataContainer getInstance()
{
if (_singleton == null)
_singleton = new DataContainer();
return _singleton;
}
public synchronized String getDataField1()
{
return _dataField1;
}
public synchronized void setDataField1(String dataField1)
{
_dataField1 = dataField1;
}
}
Then in the server side implementation of your RPC call you could do something like:
public void sendDomesticData(String product, String dma, String yrmnths, String dist, String metrics)
{
DataContainer.getInstance().setDataField1(product);
}
This way, if there are multiple servlet instances they will all share the singleton instance of DataContainer, thus giving you a place to store your data globally. I hope this will help you.

Jersey: Error when a class has both JAX-RS and JAX-WS annotations

Using Jersey 1.7, JAX-WS 2.2.3, Tomcat 6.0.30 and the following method declaration prevents Jersey servlet to start:
#POST
#Produces("text/plain")
public void postIt(#WebParam(name = "paramOne") final String paramOne,
final String paramTwo) {
// ...
}
The generated exception is:
SEVERE: Missing dependency for method public
java.lang.String com.sun.jersey.issue.MyResource.postIt(
java.lang.String,java.lang.String) at parameter at index 0
SEVERE: Method, public void
com.sun.jersey.issue.MyResource.postIt(
java.lang.String,java.lang.String),
annotated with POST of resource,
class com.sun.jersey.issue.MyResource,
is not recognized as valid resource method.
If the #WebParam annotation is removed, it all works fine.
Now, please have in mind that I am not trying to work with mere strings, rather, I am migrating complicated Objects that got marshalled/unmarshalled using SOAP to RESTful services, but I must provide both interfaces for a while, without breaking the previous WASDs. The method is just a minimalistic scenario.
Has any of you any idea of the status of this? Has it been fixed? Suggestions?
The specification is clear on this. Section 3.3.2.1 tells us that:
Resource methods MUST NOT have more
than one parameter that is not
annotated with one of the above listed
annotations.
The above listed annotations are the JAX-RS parameter annotations: #QueryParam, #MatrixParam, etc.
There is, however, a Jersey specific way to solve this problem. Using InjectableProvider. So, a method that defines two non-JAX-RS parameters:
#POST
public void postIt(#CustomInjectable final Customer customer,
final Transaction transaction) {
// ...
}
Of course, we have to code the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface CustomInjectable {
}
An implementation of InjectableProvider that knows how to provide Customers:
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
import com.sun.jersey.api.model.Parameter;
#Provider
public class CustomerInjectableProvider implements
InjectableProvider<CustomInjectable, Parameter> {
// you can use #Context variables, as in any Provider/Resource
#Context
private Request request;
public ComponentScope getScope() {
// ComponentScope.Singleton, Request or Undefined
}
public Injectable getInjectable(ComponentContext i,
CustomInjectable annotation,
Parameter param) {
Injectable injectable = null;
if (Customer.getClass().isAssignableFrom(param.getParameterClass()) {
injectable = getInjectable();
}
return injectable;
}
private Injectable getInjectable() {
return new Injectable<Customer>() {
public Customer getValue() {
// parse the customer from request... or session... or whatever...
}
};
}
}
But, Jersey considers only the last annotation (see JERSEY-ISSUE-731), so be careful.
And, a more portable way (if you do care about that, anyway):
// simple bean
public class CustomerWithTransaction {
private Customer customer;
private Transaction transaction;
// getters and setters
}
Then change the method to:
#POST
public void postIt(CustomerWithTransaction customerWithTransaction) {
// ...
}
Then create your own MessageBodyReader for CustomerWithTransaction, where you can also access any context variables (request, headers, etc.).

Categories

Resources