GithubHelp home page GithubHelp logo

jhannes / action-controller Goto Github PK

View Code? Open in Web Editor NEW
7.0 4.0 3.0 3.89 MB

Action Controller micro framework for easy REST backends

License: Apache License 2.0

Java 99.48% HTML 0.52%
java http annotations rest library server

action-controller's Introduction

Apache 2.0 License Maven Central Javadoc Build Status Coverage Status Vulnerability scan

Action Servlet micro REST framework

Action Servlet Framework lets you create simple REST Controllers with minimum of magic.

Example API:

public class MyApiServlet extends ApiServlet {

    public MyApiServlet() {
        super(new MyApiController());
    }
}


public class MyApiController {

    @GET("/v1/api/objects")
    @JsonBody
    public List<SomePojo> listObjects(
        @RequestParam("query") Optional<String> query,
        @RequestParam("maxHits") Optional<Integer> maxHits
    ) {
        // ... this is up to you
    }

    @GET("/v1/api/objects/:id")
    @JsonBody
    public SomePojo getObject(@PathParam("id") UUID id) {
        // ... this is up to you
    }

    @POST("/v1/api/objects/")
    @SendRedirect
    public String postData(
        @JsonBody SomePojo myPojo,
        @SessionParameter("user") Optional<User> user
    ) {
        // ... do your thing
        return "/home/";
    }
    
    @GET("/oauth2callback")
    @SendRedirect
    public String establishUserSession(
            @RequestParam("code") String authenticationCode,
            @SessionParameter(value = "userProfile", invalidate=true) Consumer<UserProfile> setUserProfile
    ) {
        // ...
        setUserProfile.apply(newlyLoggedInUser);
        return "/profile";
    }

}

The inner workings

The magic that makes Action Controller work is the annotations like @PathParam and @JsonBody. The set of annotations is actually extensible. Here's how @RequestParam is defined:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@HttpParameterMapping(RequestParam.RequestParameterMappingFactory.class)
public @interface RequestParam {

    String value();
}

@Retention tells javac to keep the information about the annotation available for reflection (by default, annotations are only used by the compiler). @Target tells javac to only allow this annotation on method parameters (as opposed to, for example class declarations).

@HttpParameterMapping tells Action Controller to use this annotation to resolve the value of a action method parameter. The RequestParam.RequestParameterMappingFactory describes what Action Controller should do with the annotation. Here's how it's defined:

public class RequestParameterMappingFactory extends HttpRequestParameterMappingFactory<RequestParam> {
        @Override
        public HttpRequestParameterMapping create(RequestParam annotation, Parameter parameter) {
            String name = annotation.value();
            return (exchange) -> exchange.getParameter(name, parameter);
        }
}

Action Servlet instantiates the mapping factory with a default constructor and invokes create, which lets the factory set up the mapping with properties from the annotation. The mapper itself takes an ApiHttpExchange (which encapsulates the HTTP request and the response) and returns the value to use for the method parameter on the action controller.

That's really all there it to it! :-)

Running with Jetty

import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContext;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;

public class MyServer {
    public class MyListener implements ServletContextListener {

        @Override
        public void contextInitialized(ServletContextEvent sce) {
            ServletContext context = sce.getServletContext();
            context.addServlet("api", new ApiServlet(new MyApiController())).addMapping("/api/*");
        }
    
        @Override
        public void contextDestroyed(ServletContextEvent sce) {    
        }
    }


    public static void main(String[] args) {
        Server server = new Server(8080);
        ServletContextHandler handler = new ServletContextHandler();
        handler.setContextPath("/demo");
        handler.addEventListener(new MyListener());
        server.setHandler(handler);
        server.start();
    }
}

Running with web.xml

  1. Implemement a ServletContextListener which creates ApiServlet
  2. Add the ServletContextListener to your web.xml file
import javax.servlet.ServletContextListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContext;

public class MyListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        context.addServlet("api", new ApiServlet(new MyApiController())).addMapping("/api/*");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {    
    }
}
<web-app>
  <listener>
    <listener-class>
       MyListener
    </listener-class>
  </listener>
</web-app>

Running with JDK HttpServer

import com.sun.net.httpserver.HttpServer;
import org.actioncontroller.httpserver.ApiHandler;

public class MyServer {
    public static void main(String[] args){
          HttpServer httpServer = HttpServer.create(new InetSocketAddress("localhost", 8080), 0);
          httpServer.createContext("/demo/api", new ApiHandler(new MyApiController()));
          httpServer.start();
    }
}

Api client

interface MyApi {
    
    @GET("/v1/api/objects")
    @JsonBody
    List<SomePojo> listObjects(
        @RequestParam("query") Optional<String> query,
        @RequestParam("maxHits") Optional<Integer> maxHits
    );

    @GET("/v1/api/objects/:id")
    @JsonBody
    SomePojo getObject(@PathParam("id") long id);
}

public class ApiClientDemo {
    public static void main(String[] args){
        HttpURLConnectionApiClient client = new HttpURLConnectionApiClient("http://localhost:8080/api");
        MyApi myApi = ApiClientProxy.create(MyApi.class, httpClient);
        
        // Will perform HTTP GET of http://localhost:8080/api/v1/api/objects/5001
        SomePojo object = myApi.getObject(50001);
    }
}

ConfigObserver

ConfigObserver is a revolutionary way to think of application configuration. ConfigObserver monitors the configuration values and calls back to you application when a configuration value you care about is changed. This way, your application can easily hot-reload configuration values.

Example:

public class MyApplication {
    
    private String myConfigValue;
    private DataSource dataSource;
    private ServerSocket serverSocket;

    public MyApplication(ConfigObserver config) {
        config.onStringValue("myConfigValue", null, v -> this.myConfigValue = v);
        config.onPrefixedValue("dataSource", DataSourceConfig::create, dataSource -> this.dataSouce = dataSource);
        config.onInetSocketAddress("serverAddress", 10080,
                address -> {
                    if (serverSocket != null) serverSocket.close();
                    serverSocket = new ServerSocket(address);
                    startServerSocket(serverSocket);
                });
    }

    public static void main(String[] args){
      ConfigObserver config = new ConfigObserver(new File("."), "myApp");
      new MyApplication(config);
    }
}

TODO

  • Log payloads
  • Split HttpClientParameterMapping from HttpParameterMapping?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.