Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runtime endpoint route uris resolving #1098

Open
renatorroliveira opened this issue Sep 13, 2017 · 3 comments
Open

Runtime endpoint route uris resolving #1098

renatorroliveira opened this issue Sep 13, 2017 · 3 comments

Comments

@renatorroliveira
Copy link
Collaborator

I was digging into the VRaptor code and I was unable to figure out a way to define the controller's endpoints paths (URI's from the @Path/@Get/@post annotations) at runtime.
In some situations this can be very useful.
I was trying to isolate some common controllers as libraries, but the URI's of those controllers methods must be defined as static strings that must be compilation time resolvable.
Am I missing something here? It is already possible to do that?

I can think of some other situations that this can be useful, like dynamic generation of URI's in multi instance system, some API publication patterns and others.

Do you think this is an interesting feature for VRaptor? If so, I can propose an implementation of this.

@Turini
Copy link
Member

Turini commented Sep 17, 2017

@renatorroliveira thank you for bringing this up

However, I cant think in a situation where I'd use this in my projects.
could you provide an use case?

@renatorroliveira
Copy link
Collaborator Author

Well, it is very specific indeed.
But let me explain my actual use case.

I have several projects that share the same logic of user control and multi tenant implementation. However they are distinct projects and a I can't use these logics as a auth/authz microservice, I must embed them into the project and deploy them on different servers. So, I came with the proposal to isolate that logic into a library/plugin. At this point no problem.

The thing is that I want to include the API controllers within the library too. When I was doing that I noticed that there are many approaches for this, none of them is intuitive. The lack of an easy way to register/resolve routes at runtime makes this implementation ugly. I want to include the controllers and dynamically decides if a given route will be included or not in the current application instance based on runtime data. For example, I can have a configuration bean that defines which routes will be included in the internal router:

public interface MyRuntimeRoutesConfiguration {
	public String getRoutesPrefix();
	public boolean isUserControlEnabled();
	public boolean isMultiTenantEnabled();
}

Then I inject this bean into an override of the path resolver:

public interface RuntimeRouteResolver {
	String[] getURIsFor(Path pathAnn);
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimePath {
	Class<? extends RuntimeRouteResolver> value();
}
public class PrefixedRouteResolver implements RuntimeRouteResolver {

	private MyRuntimeRoutesConfiguration config;
	
	public PrefixedRouteResolver() {
		this.config = CDI.current().select(MyRuntimeRoutesConfiguration.class).get();
	}
	
	@Override
	public String[] getURIsFor(Path pathAnn) {
		String[] uris = pathAnn.value();
		
		for (int i = 0; i < uris.length; i++) {
			if (!GeneralUtils.isEmpty(uris[i])) {
				uris[i] = this.config.getRoutesPrefix() + uris[i];
			}
		}
		
		return uris;
	}

}
@ApplicationScoped
@Specializes
public class RuntimeRouteParser extends PathAnnotationRoutesParser {
	
	private static final Logger LOG = Logger.getLogger(RuntimeRouteParser.class);
	
	/** 
	 * @deprecated CDI eyes only
	 */
	protected RuntimeRouteParser() {
		this(null, null);
	}

	@Inject
	public RuntimeRouteParser(Router router, ReflectionProvider reflectionProvider) {
		super(router, reflectionProvider);
	}

	@Override
	protected String[] getURIsFor(Method javaMethod, Class<?> type) {
		if (javaMethod.isAnnotationPresent(RuntimePath.class) && javaMethod.isAnnotationPresent(Path.class)) {
			Path pathAnn = javaMethod.getAnnotation(Path.class);
			RuntimePath runtimePathAnn = javaMethod.getAnnotation(RuntimePath.class);
			
			RuntimeRouteResolver resolve;
			String[] uris = new String[] {};
			try {
				resolve = runtimePathAnn.value().newInstance();
				uris = resolve.getURIsFor(pathAnn);
			} catch (InstantiationException | IllegalAccessException e) {
				LOG.errorf(e, "Error instantiating runtime path resolver.");
				checkArgument(false, "Error instantiating runtime path resolver.");
			}

			checkArgument(uris.length > 0, "You must specify at least one path on @Path at %s", javaMethod);
			checkArgument(getUris(javaMethod).length == 0,
					"You should specify paths either in @Path(\"/path\") or @Get(\"/path\") (or @Post, @Put, @Delete), not both at %s", javaMethod);

			fixURIs(type, uris);
			return uris;
		}
		return super.getURIsFor(javaMethod, type);
	}
}

This is an initial solution I came up with, but I really believe that this logic could be added to the framework (or something similar) because it enables more flexible approaches for some architectural problems we can face.

Of course it is a little specific, but I think flexibility isn't a problem. What do you think?

@renatorroliveira
Copy link
Collaborator Author

This is just an example that adds runtime resolved prefix to the endpoint's URI. Some project that uses the library can specializes the configuration bean and change prefix and/or disable/enable the multi tenant related endpoints for example.

I hope this example shows the flexibility this idea brings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants