Skip to content

Commit

Permalink
Add support for annotated CoreHandlers using Runtime retention only.
Browse files Browse the repository at this point in the history
  • Loading branch information
codingchili committed Aug 18, 2017
1 parent 913eec8 commit 28d5d0e
Show file tree
Hide file tree
Showing 6 changed files with 446 additions and 237 deletions.
54 changes: 34 additions & 20 deletions core/main/java/com/codingchili/core/listener/CoreHandler.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
package com.codingchili.core.listener;

/**
* @author Robin Duda
* <p>
* A simplified handler that may be deployed directly.
*/
public interface CoreHandler extends CoreDeployment {

/**
* Handles an incoming request without exception handling.
*
* @param request the request to be handled.
*/
void handle(Request request);

/**
* @return the address of the handler.
*/
String address();
package com.codingchili.core.listener;

import com.codingchili.core.protocol.Address;

/**
* @author Robin Duda
* <p>
* A simplified handler that may be deployed directly.
*/
public interface CoreHandler extends CoreDeployment {

/**
* Handles an incoming request without exception handling.
*
* @param request the request to be handled.
*/
void handle(Request request);

/**
* @return the address of the handler. If not implemented the @Address
* annotation will be used, if missing an error is thrown.
*
* Could potentially lead to Runtime errors but is allowed here as
* this is called during deployment. Reconsider this decision later.
*/
default String address() {
Address annotation = getClass().getAnnotation(Address.class);
if (annotation != null) {
return annotation.value();
} else {
throw new RuntimeException("Class " + getClass().getName() + " does not" +
" implement CoreHandler::address nor is annotated with @Address.");
}
}
}
15 changes: 15 additions & 0 deletions core/main/java/com/codingchili/core/protocol/Address.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codingchili.core.protocol;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Alternate way of specifying a handlers listening address.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Address {
String value();
}
15 changes: 15 additions & 0 deletions core/main/java/com/codingchili/core/protocol/Private.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codingchili.core.protocol;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that the annotated method requires authentication.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Private {
String value();
}
249 changes: 146 additions & 103 deletions core/main/java/com/codingchili/core/protocol/Protocol.java
Original file line number Diff line number Diff line change
@@ -1,103 +1,146 @@
package com.codingchili.core.protocol;

import com.codingchili.core.protocol.exception.AuthorizationRequiredException;
import com.codingchili.core.protocol.exception.HandlerMissingException;

import io.vertx.core.json.JsonObject;

import static com.codingchili.core.configuration.CoreStrings.*;

/**
* @author Robin Duda
* <p>
* Maps packet data to handlers and manages authentication status for handlers.
*/
public class Protocol<Handler extends RequestHandler> {
private final AuthorizationHandler<Handler> handlers = new AuthorizationHandler<>();

/**
* Returns the route handler for the given target route and its access level.
*
* @param access the access level the request is valid for.
* @param route the handler route to find
* @return the handler that is mapped to the route and access level.
* @throws AuthorizationRequiredException when authorization level is not fulfilled for given route.
* @throws HandlerMissingException when the requested route handler is not registered.
*/
public Handler get(Access access, String route) throws AuthorizationRequiredException, HandlerMissingException {
if (handlers.contains(route)) {
return handlers.get(route, access);
} else {
return handlers.get(ANY, access);
}
}

/**
* Returns the route handler for the given target route and its access level.
*
* @param route the handler route to find.
* @return the handler that is mapped to the route.
* @throws AuthorizationRequiredException when authorization level is not fulfilled for the given route.
* @throws HandlerMissingException when the requested route handler is not registered.
*/
public Handler get(String route) throws AuthorizationRequiredException, HandlerMissingException {
return get(Access.AUTHORIZED, route);
}

/**
* Registers a handler for the given route.
*
* @param route the route to register a handler for.
* @param handler the handler to be registered for the given route.
* @return the updated protocol specification for fluent use.
*/
public Protocol<Handler> use(String route, Handler handler) {
use(route, handler, Access.AUTHORIZED);
return this;
}

/**
* Registers a handler for the given route with an access level.
*
* @param route the route to register a handler for.
* @param handler the handler to be registered for the given route with the access level.
* @param access specifies the authorization level required to access the route.
* @return the updated protocol specification for fluent use.
*/
public Protocol<Handler> use(String route, Handler handler, Access access) {
handlers.use(route, handler, access);
return this;
}

/**
* @return returns a list of all registered routes on the protoocol.
*/
public ProtocolMapping list() {
return handlers.list();
}

/**
* Creates a response object given a response status.
*
* @param status the status to create the response from.
* @return a JSON encoded response packed in a buffer.
*/
public static JsonObject response(ResponseStatus status) {
return new JsonObject()
.put(PROTOCOL_STATUS, status);
}

/**
* Creates a response object given a response status and a throwable.
*
* @param status the status to include in the response.
* @param e an exception that was the cause of an abnormal response status.
* @return a JSON encoded response packed in a buffer.
*/
public static JsonObject response(ResponseStatus status, Throwable e) {
return new JsonObject()
.put(PROTOCOL_STATUS, status)
.put(PROTOCOL_MESSAGE, e.getMessage());
}
}

package com.codingchili.core.protocol;

import com.codingchili.core.listener.CoreHandler;
import com.codingchili.core.listener.Request;
import com.codingchili.core.protocol.exception.AuthorizationRequiredException;
import com.codingchili.core.protocol.exception.HandlerMissingException;
import io.vertx.core.json.JsonObject;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static com.codingchili.core.configuration.CoreStrings.*;

/**
* @author Robin Duda
* <p>
* Maps packet data to handlers and manages authentication status for handlers.
*/
public class Protocol<Handler extends RequestHandler> {
private final AuthorizationHandler<Handler> handlers = new AuthorizationHandler<>();

public Protocol() {}

/**
* Creates a protocol by mapping @Public and @Private annotated methods.
*
* @param handler contains methods to be mapped.
*/
public Protocol(CoreHandler handler) {
Method[] methods = handler.getClass().getDeclaredMethods();

for (Method method : methods) {
Annotation annotation = method.getAnnotation(Public.class);

if (annotation != null) {
use(((Public) annotation).value(), wrap(handler, method), Access.PUBLIC);
} else {
annotation = method.getAnnotation(Private.class);
if (annotation != null) {
use(((Private) annotation).value(), wrap(handler, method), Access.AUTHORIZED);
}
}
}
}

@SuppressWarnings("unchecked")
private Handler wrap(Object coreHandler, Method method) {
return (Handler) new RequestHandler<Request>() {
@Override
public void handle(Request request) {
try {
method.invoke(coreHandler, request);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
};
}

/**
* Returns the route handler for the given target route and its access level.
*
* @param access the access level the request is valid for.
* @param route the handler route to find
* @return the handler that is mapped to the route and access level.
* @throws AuthorizationRequiredException when authorization level is not fulfilled for given route.
* @throws HandlerMissingException when the requested route handler is not registered.
*/
public Handler get(Access access, String route) throws AuthorizationRequiredException, HandlerMissingException {
if (handlers.contains(route)) {
return handlers.get(route, access);
} else {
return handlers.get(ANY, access);
}
}

/**
* Returns the route handler for the given target route and its access level.
*
* @param route the handler route to find.
* @return the handler that is mapped to the route.
* @throws AuthorizationRequiredException when authorization level is not fulfilled for the given route.
* @throws HandlerMissingException when the requested route handler is not registered.
*/
public Handler get(String route) throws AuthorizationRequiredException, HandlerMissingException {
return get(Access.AUTHORIZED, route);
}

/**
* Registers a handler for the given route.
*
* @param route the route to register a handler for.
* @param handler the handler to be registered for the given route.
* @return the updated protocol specification for fluent use.
*/
public Protocol<Handler> use(String route, Handler handler) {
use(route, handler, Access.AUTHORIZED);
return this;
}

/**
* Registers a handler for the given route with an access level.
*
* @param route the route to register a handler for.
* @param handler the handler to be registered for the given route with the access level.
* @param access specifies the authorization level required to access the route.
* @return the updated protocol specification for fluent use.
*/
public Protocol<Handler> use(String route, Handler handler, Access access) {
handlers.use(route, handler, access);
return this;
}

/**
* @return returns a list of all registered routes on the protoocol.
*/
public ProtocolMapping list() {
return handlers.list();
}

/**
* Creates a response object given a response status.
*
* @param status the status to create the response from.
* @return a JSON encoded response packed in a buffer.
*/
public static JsonObject response(ResponseStatus status) {
return new JsonObject()
.put(PROTOCOL_STATUS, status);
}

/**
* Creates a response object given a response status and a throwable.
*
* @param status the status to include in the response.
* @param e an exception that was the cause of an abnormal response status.
* @return a JSON encoded response packed in a buffer.
*/
public static JsonObject response(ResponseStatus status, Throwable e) {
return new JsonObject()
.put(PROTOCOL_STATUS, status)
.put(PROTOCOL_MESSAGE, e.getMessage());
}
}

15 changes: 15 additions & 0 deletions core/main/java/com/codingchili/core/protocol/Public.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.codingchili.core.protocol;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Indicates that the annotated method does not require authentication.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Public {
public String value();
}
Loading

0 comments on commit 28d5d0e

Please sign in to comment.