Dropwizard + Jersey : "Not inside a request scope" when creating custom annotation - java

I have a simple Dropwizard 0.8.1 REST service that pulls in Jersey 2.17. Upstream of the REST/Jetty service I have some authentication service that adds some nice authorization information to the HTTP Header that gets passed to my Dropwizard app.
I would love to be able to create a custom annotation in my Resource that hides all the messy header-parsing-to-POJO garbage. Something like this:
#Path("/v1/task")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class TaskResource {
#UserContext // <-- custom/magic annotation
private UserContextData userContextData; // <-- holds all authorization info
#GET
public Collection<Task> fetch() {
// use the userContextData to differentiate what data to return
}
I've spent the last day looking around stackoverflow and found several other people who had the same issue and appeared (?) to get some satisfaction, but I can't seem to avoid getting a "Not inside a request scope" stack trace when I try to do this.
So I stashed all my changes and tried to implement the example provided in sections 22.1 and 22.2 by the Jersey documentation directly: https://jersey.java.net/documentation/2.17/ioc.html
Following along with their example (but in my Dropwizard app), I'm trying to get a "#SessionInject" annotation in my Resource, but it also blows up with "Not inside a request scope" stack trace each time. What am I doing wrong here?
Resource:
#Path("/v1/task")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class TaskResource {
private final TaskDAO taskDAO;
#Context
private HttpServletRequest httpRequest;
#SessionInject
private HttpSession httpSession;
public TaskResource(TaskDAO taskDAO) {
this.taskDAO = taskDAO;
}
#GET
public Collection<Task> fetch(#SessionInject HttpSession httpSession) {
if (httpSession != null) {
logger.info("TOM TOM TOM httpSession isn't null: {}", httpSession);
}
else {
logger.error("TOM TOM TOM httpSession is null");
}
return taskDAO.findAllTasks();
}
The SessionInjectResolver:
package com.foo.admiral.integration.jersey;
import com.foo.admiral.integration.core.SessionInject;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SessionInjectResolver implements InjectionResolver<SessionInject> {
private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);
#Inject
#Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
InjectionResolver<Inject> systemInjectionResolver;
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (HttpSession.class == injectee.getRequiredType()) {
return systemInjectionResolver.resolve(injectee, handle);
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
}
The HttpSessionFactory:
package com.foo.admiral.integration.jersey;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Singleton
public class HttpSessionFactory implements Factory<HttpSession> {
private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);
private final HttpServletRequest request;
#Inject
public HttpSessionFactory(HttpServletRequest request) {
logger.info("Creating new HttpSessionFactory with request");
this.request = request;
}
#Override
public HttpSession provide() {
logger.info("Providing a new session if one does not exist");
return request.getSession(true);
}
#Override
public void dispose(HttpSession t) {
}
}
The annotation:
package com.foo.admiral.integration.core;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.FIELD})
public #interface SessionInject {
}
And, finally, the binding in the Dropwizard Application class:
#Override
public void run(TodoConfiguration configuration, Environment environment) throws Exception {
...
environment.jersey().register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(HttpSessionFactory.class).to(HttpSession.class);
bind(SessionInjectResolver.class)
.to(new TypeLiteral<InjectionResolver<SessionInject>>() { })
.in(Singleton.class);
}
});
Ye old stack trace:
Caused by: java.lang.IllegalStateException: Not inside a request scope.
at jersey.repackaged.com.google.common.base.Preconditions.checkState(Preconditions.java:149)
at org.glassfish.jersey.process.internal.RequestScope.current(RequestScope.java:233)
at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:158)
at org.jvnet.hk2.internal.MethodInterceptorImpl.invoke(MethodInterceptorImpl.java:74)
at org.jvnet.hk2.internal.MethodInterceptorInvocationHandler.invoke(MethodInterceptorInvocationHandler.java:62)
at com.sun.proxy.$Proxy72.getSession(Unknown Source)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:29)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:14)
Some clues that may be useful:
1) I'm noticing is that the logging statements in my HttpSessionFactory are never getting fired, so I don't think the Factory is correctly identified to DropWizard.
2) If I change the annotation to be a Parameter instead of a Field and move the use of the annotation into the fetch( ) method signature like this, it doesn't throw the stack trace (but the httpSession is still null, presumably because the Factory isn't firing...)
public Collection<Task> fetch(#SessionInject HttpSession httpSession) {
3) It doesn't appear to matter if I "register" the binder with environment.jersey().register() or environment.jersey().getResourceConfig().register()... they appear to do the same thing.
Do you see any obvious problems? Thanks in advance!

This is weird behavior. But what looks like is going on is the following
You have registered TaskResource as an instance and not as a .class. This I'm pretty sure of (though you have not mentioned).
register(new TaskResource());
/* instead of */
register(TaskResource.class);
Doing the former, it set the resource in a singleton scope. The latter in a request scope (unless annotated otherwise - see below)
When the resource model is loading it sees the TaskResource is a singleton, and that the HttpServletRequest is in a request scope. Either that or that the factory is in a per request scope. I'm guessing one of the two.
I thought that it might actually be a scope issue, as mentioned in the error message, but what I'm pretty sure of is that at runtime, it will get handled with a thread local proxy, because of the lesser scope.
You can see it fixed by registering the TaskResource as a class, and then annotating the TaskResource with #Singleton. This is if you actually do want the resource class to be a singleton. If not, then just leave off the #Singleton.
The odd thing to me is that it the fact that it fails on startup when the resource is explicitly instantiated on startup, but works when the framework loads on the first request (which is what happens when you register it as a class). They are both still in a singleton scope.
One thing you might want to take into consideration is whether you actually want the resource to be a singleton or not. You do have to worry about thread safety issues with singletons, and there are are some other limitations. Personally, I prefer to keep them in a request scope. You would have to do some performance testing to see if there is much of an impact for your application.
UPDATE
For parameter injection you may want to take a look at this post
UPDATE 2
See Also
jersey 2 context injection based upon HttpRequest without singleton. My answer should shed some more light.

Related

Need to return value from scheduled method in Spring MVC

I am writing scheduler in my web application for notification purpose, the task of my scheduler is simple, It will hit the third party centralised database and look for the availability of data, if data is available then it returns true otherwise false.
But I am stuck here, I want to show the notification on based on the result (true/false)returning by my scheduler, but I am not able to think, how do I implement the same? I thought of bind the variable in session, but because it is time even so session is not possible here.
Suppose scheduler returning true, now I want this value inside my JSP page(Dashboard page) where I can able to show the message that "Data is available" in user's dashboard. I need this value to check condition
if(true)
"data is available"
else
no notification
Please see my code and suggest me.
package com.awzpact.uam.scheduler;
import com.awzpact.prayas.dao.HRMSPickSalaryDataDAO;
import com.awzpact.uam.domain.SalaryDetailReport;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class PayrollDataNotificationScheduler {
private static boolean AVAIL_STATUS = false;
private static final Logger LOGGER = Logger.getLogger(PayrollDataNotificationScheduler.class);
public boolean checkDataAvailability() {
try {
List<SalaryDetailReport> list = salaryDataDAO.findAll();
if (list.size() > 0) {
AVAIL_STATUS = true;
return AVAIL_STATUS;
}
return false;
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("Data is not available for migrate");
return false;
}
}
#Autowired
HRMSPickSalaryDataDAO salaryDataDAO;
}
You run your scheduled task periodically if there's some data retrieved - you save it to your DB.
// in your scheduled #Component
#Autowired
private SomeDataDAO someDataDAO;
#Scheduled(cron = "...")
public void fetchThirdPartyData() {
SomeData thirdPartyData = getThirdPartyData();
someDataDAO.save(thirdPartyData);
}
private SomeData getThirdPartyData() {
// calling their API...
}
Then you create a controller which is going to get the data from db (if exists, notice the Optional interface - you can use this in your DAO method)
// a rest controller
#RestController
#RequestMapping("/someData")
public class SomeController {
#Autowired
private SomeDataDAO someDataDAO;
#GetMapping
public SomeData getSomeData() {
return someDataDao.getSomeData().orElse(null);
}
}
Now, in your fronted you do some AJAX call, depending on what you're using there and then you can do your check and print the message.
Scheduling means that you want to make some actions on the schedule basis.
Waiting for response looks more like request/response communication between client and server.
To check that data is available - it's better to use simple method invocation via REST Controller and don't use a scheduler at all.

Play Framework REST API call

I'm new to Play framework, and trying to use JavaWS to make a call to a RESTful API. I've been struggling a lot with it. This is what I have so far:
This code is based on the JavaWS documentation (which I found quite confusing), and is meant to make the request. I think it works by returing a completion stage of an 'ok' result which contains a string that is the result of converting the response to text.
import javax.inject.Inject;
import com.fasterxml.jackson.databind.JsonNode;
import play.mvc.*;
import play.libs.ws.*;
import java.util.concurrent.*;
import static play.mvc.Results.ok;
public class MyClient implements WSBodyReadables, WSBodyWritables {
private final WSClient ws;
#Inject
public MyClient() {
this.ws = ws;
}
public CompletionStage<Result> index() {
return ws.url("http://example.com").get().thenApply(response ->
ok(response.asText())
);
}
}
This code is then called from a controller:
public Result call(){
MyClient client = new MyClient();
try {
return client.index()
.toCompletableFuture()
.get();
} catch(Exception e){
Logger.error("ah fuck");
}
return internalServerError();
}
I'm currently getting an error which says "variable ws might not have been initialized" which makes sense because I did not initialize ws. I can't figure out how to properly initialize a WSClient instance, nor do I really understand what comes after that. Any help would be greatly appreciated.
Thanks.
Alternatively, you can use Feign library from Netflix to create Rest client.
#rkj had it right:
inject #Inject WSClient ws; in your controller and then pass ws instance to >MyClient class and access it from there. MyClient client = new MyClient(this.ws);
That plus a few little bugs and it worked. Thanks!

How to inject MessagesApi outside a controller?

I have a simple class service and it is being injected beautifully on my application. However, I am trying to inject the messages api to read a few keys on my message files but I am getting the same error:
1) Could not find a suitable constructor in play.i18n.Messages.
Classes must have either one (and only one) constructor annotated with
#Inject or a zero-argument constructor that is not private. at
play.i18n.Messages.class(Messages.java:61)
public class SampleServiceImpl implements SampleService {
private MessagesApi messages;
#Inject
public SampleServiceImpl(MessagesApi messages){
this.messages = messages;
}
}
#ImplementedBy(SampleServiceImpl.class)
public interface SampleService {
}
Is the a way to do that by DI?
Edit:
I was able to get the value by doing this but it does not look elegant, any options ?
messages.get(new Lang(new Locale("en")), "ticket.form.title")
The reason of such "non-elegancy" is that language (and Messages) depends on the request.
The Default behavior is that Messages detect current language on the base of cookie, available languages and default language.
Someware under the hood: Messages messages = messagesApi.preferred(request());
Will select a language from the request, based on the languages
available, and fallback to the default language if none of the
candidates are available.
Fortunately, there is a special method that you can use to initialize Messages with the language you want:
import play.i18n.MessagesApi;
import play.i18n.Messages;
import play.i18n.Lang;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
...
Locale englishLocale = new Locale("en");
Lang englishLang = new Lang(englishLocale);
List<Lang> prefferedLangs = Arrays.asList(englishLang);
Messages messagesCustom = messagesApi.preferred(prefferedLangs);
// the time for elegancy
messages.at("ticket.form.title");
I advise you to create tiny MessagesApiCustom service, that will do this few strings of code in the initialization time and then will proxy the at method to the messages.at, so it will look like:
public class SampleServiceImpl implements SampleService {
private MessagesApiCustom messages;
#Inject
public SampleServiceImpl(MessagesApiCustom messages){
this.messages = messages;
}
private void doSomeStuff(){
Strign message = messages.at("message.key")
}
}
You can go further, and implement language selection based on annotation:
#Named("FR")
private MessagesApiCustom messages;
Of course, if you need the dynamic language selection, then just use the method that is already present in Play.

#AroundInvoke interceptor is called twice on a #WebService class

Summary
#AroundInvoke interceptor is called twice on a #WebService class,
if the intercepted method is called from outside of the application via endpoint as a SOAP web service.
If the very same method is called internally from another bean, it's called only once (as I would expect).
The intercepted method itself is always called only once!
Question 1: Can I make the interceptor to be called only once?
Question 2: If I cannot, is there a transferable (server independent) way to decide in which interceptor I am, so I can ignore the redundant one?
Question 3: Is this behaviour common (and defined and described in some documentation),
or is it dependent on my specific environment (JBoss EAP 6.4.0)?
Observation:
The two calls are not in the same interceptor chain.
It is not the same instance of the interceptor class.
The implementation class of the InvocationContext is different for both the calls.
It's funny that one of the contextData, the InvocationContext's field for passing data along the interceptor chain, is not an instance of the HashMap, but WrappedMessageContext, but it does not wrap the other contextData anyway.
Minimal reproducible code
(I removed the package name.)
MyEndpoint interface
import javax.jws.WebService;
#WebService
public interface MyEndpoint {
public static final String SERVICE_NAME = "MyEndpointService";
public String getHello();
}
MyEndpointImpl class
import javax.interceptor.Interceptors;
import javax.jws.WebService;
#WebService(endpointInterface = "MyEndpoint", serviceName = MyEndpoint.SERVICE_NAME)
#Interceptors({TestInterceptor.class})
public class MyEndpointImpl implements MyEndpoint {
#Override
public String getHello() {
System.out.println("MyEndpointImpl.getHello() called");
return "Hello";
}
}
TestInterceptor class
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
public class TestInterceptor {
#AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
System.out.println("Interceptor called");
return ic.proceed();
}
}
Output
Interceptor called
Interceptor called
MyEndpointImpl.getHello() called
More details
To get more runtime information, I added more logging.
MyEndpointImpl class
import java.lang.reflect.Method;
import java.util.Map;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestInterceptor {
private static Logger logger = LoggerFactory.getLogger(TestInterceptor.class);
private static int callCnt = 0;
#AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
final String interceptorClass = this.toString();
final String invocationContextClass = ic.getClass().getName();
final Method method = ic.getMethod();
final String calledClass = method.getDeclaringClass().getName();
final String calledName = method.getName();
final String message = String.format(
"%n INTERCEPTOR: %s%n InvocationContext: %s%n %s # %s()",
interceptorClass, invocationContextClass, calledClass, calledName);
logger.info(message);
final int call = ++callCnt;
final Map<String, Object> contextData = ic.getContextData();
contextData.put("whoami", call);
logger.info("BEFORE PROCEED {}, {}", call, contextData);
final Object ret = ic.proceed();
logger.info("AFTER PROCEED {}, {}", call, contextData);
return ret;
}
}
Output
INTERCEPTOR: TestInterceptor#74c90b72
InvocationContext: org.jboss.invocation.InterceptorContext$Invocation
MyEndpointImpl # getHello()
BEFORE PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext#2cfccb1d
INTERCEPTOR: TestInterceptor#5226f6d8
InvocationContext: org.jboss.weld.interceptor.proxy.InterceptorInvocationContext
MyEndpointImpl # getHello()
BEFORE PROCEED 2, {whoami=2}
MyEndpointImpl.getHello() called
AFTER PROCEED 2, {whoami=2}
AFTER PROCEED 1, org.apache.cxf.jaxws.context.WrappedMessageContext#2cfccb1d
I cannot answer your questions directly, but maybe some clarification about the contexts may help you.
The Java EE JAX-WS implementation varies from server to server. For Example Glassfish uses Metro and JBoss uses Apache CXF.
There are different kind of interceptors chains which allow to control programmatically the conditions before and after the request/response processing.
The interceptors for the SOAP web service calls are SOAP handlers and logical handlers (See Oracle documentation). Both can access SOAP message on different levels (the whole or only the payload).
My assumption is that your the interceptor called twice, once for accessing through HTTP/SOAP, and once for access over RMI.
In the first interceptor invocation, what you see as context is org.apache.cxf.jaxws.context.WrappedMessageContext which is a Map implementation. See WarppedMessageContext, Apache CXF web service context. It is invoked for HTTP/SOAP access.
The second invocation is what you expect when using the RMI (probably triggered from Apache CXF, after the SOAP message is processed).
To avoid this you can use third class for logic implementation with interceptor defined. The existing web service implementation class will only delegate to it and will not contain interceptor annotation anymore.
Example code can be seen here: OSCM Project
I had the exact same problem and found a solution.
If you instead of using the #Interceptors style binding use a #InterceptorBinding style binding, then the interceptor is only instantiated and invoked once (at least in my case on WildFly 10.1.0.Final).
This is what your example would look like using #InterceptorBinding style.
Your custom interceptor binding annotation:
import javax.interceptor.InterceptorBinding;
...
#Inherited
#InterceptorBinding
#Retention(RUNTIME)
#Target({METHOD, TYPE})
public #interface MyInterceptorBinding {
Your endpoint:
#WebService(endpointInterface = "MyEndpoint", serviceName =
MyEndpoint.SERVICE_NAME)
#MyInterceptorBinding
public class MyEndpointImpl implements MyEndpoint {
Your interceptor:
import javax.interceptor.Interceptor;
import javax.annotation.Priority;
...
#Interceptor
#MyInterceptorBinding
#Priority(Interceptor.Priority.APPLICATION) //we use #Priority to enable this interceptor application-wide, without having to use beans.xml in every module.
public class TestInterceptor {
#AroundInvoke
private Object countCalls(InvocationContext ic) throws Exception {
System.out.println("Interceptor called");
return ic.proceed();
}
I never figured out exactly what the problem is, but I suspect that the #Interceptors style binding is valid for multiple types of interceptors (EJB and CDI) while the #InterceptorBinding style is maybe only valid for CDI interceptors.
Maybe a JAX-WS #WebService is both an EJB and a CDI bean?

Spring-security-oauth OAuthProviderTokenServices custom implementation in OAuth 1.0

I'm using Spring-security-oauth to secure a RESTful application, and I'm triying to implement a custom OAuthProviderTokenServices class in order to store tokens in a database.
All I got from the docs is:
When creating your OAuthProviderTokenServices implementation, you may
want to consider extending the RandomValueProviderTokenServices which
creates tokens via random value and handles everything except for the
persistence of the tokens. There is also an in-memory implementation
of the OAuthProviderTokenServices that may be suitable [...]
which is fine, so I created a new custom class:
package experiments;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.security.oauth.provider.token.OAuthProviderTokenImpl;
import org.springframework.security.oauth.provider.token.RandomValueProviderTokenServices;
/**
* Implementation of TokenServices that stores tokens in a database.
*
* #author Seether
*/
public class DatabaseProviderTokenServices extends RandomValueProviderTokenServices {
protected final ConcurrentHashMap<String, OAuthProviderTokenImpl> tokenStore = new ConcurrentHashMap<String, OAuthProviderTokenImpl>();
protected OAuthProviderTokenImpl readToken(String token) {
return tokenStore.get(token);
}
protected void storeToken(String tokenValue, OAuthProviderTokenImpl token) {
tokenStore.put(tokenValue, token);
}
protected OAuthProviderTokenImpl removeToken(String tokenValue) {
return tokenStore.remove(tokenValue);
}
}
which for now, as you can see, is identical to the InMemoryProviderTokenServices class.
My application uses the AccessConfirmationController from sparkl example, which is this:
package experiments;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.oauth.provider.ConsumerDetails;
import org.springframework.security.oauth.provider.ConsumerDetailsService;
import org.springframework.security.oauth.provider.token.OAuthProviderToken;
import org.springframework.security.oauth.provider.token.OAuthProviderTokenServices;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* Controller for retrieving the model for and displaying the confirmation page for access to a protected resource.
*
* #author Ryan Heaton
*/
#Controller
public class AccessConfirmationController {
private OAuthProviderTokenServices tokenServices;
private ConsumerDetailsService consumerDetailsService;
#RequestMapping("/oauth/confirm_access")
public ModelAndView getAccessConfirmation(HttpServletRequest request, HttpServletResponse response)
throws Exception {
String token = request.getParameter("oauth_token");
if (token == null) {
throw new IllegalArgumentException("A request token to authorize must be provided.");
}
OAuthProviderToken providerToken = tokenServices.getToken(token);
ConsumerDetails consumer = consumerDetailsService
.loadConsumerByConsumerKey(providerToken.getConsumerKey());
String callback = request.getParameter("oauth_callback");
TreeMap<String, Object> model = new TreeMap<String, Object>();
model.put("oauth_token", token);
if (callback != null) {
model.put("oauth_callback", callback);
}
model.put("consumer", consumer);
return new ModelAndView("access_confirmation", model);
}
public void setTokenServices(OAuthProviderTokenServices tokenServices) {
this.tokenServices = tokenServices;
}
public void setConsumerDetailsService(ConsumerDetailsService consumerDetailsService) {
this.consumerDetailsService = consumerDetailsService;
}
}
Now the question is: how do I tell my application to use my tokenServices implementation rather than the default one (which right now I belive is InMemoryProviderTokenServices)?
I tried messing around with the controller, but the fews attempt all led me to java.lang.IllegalStateExceptions.
I also noticed that there is a line in the config XML:
<oauth:token-services id="tokenServices"/>
Which might be a critical piece of the puzzle, as the related help reads:
Element for declaring and configuring an in-memory implementation of
the provider token service.
If I just remove it, I get:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'accessConfirmationController' defined in
ServletContext resource [/WEB-INF/mvc-dispatcher-servlet.xml]: Cannot
resolve reference to bean 'tokenServices' while setting bean property
'tokenServices'; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'tokenServices' is defined
Funny as answers come out of nowhere sometimes, expecially after it's been some time since you've started looking for them and you've just posted here. Oh well.
According to a code snippet randomly found here, it looks like all I needed to do was to comment out that line from the XML configuration file:
<!-- <oauth:token-services id="tokenServices"/> -->
and replace it with this one:
<beans:bean id="tokenServices" class="experiments.DatabaseProviderTokenServices" />
where the class is of course my implementation.
By doing this, the DatabaseProviderTokenServices class is used in place of the default InMemoryProviderTokenServices class.

Categories