About Spring WebClient on external onTerminate event - spring-boot

I'm running a spring-boot v2.0.3 tomcat-embedded webserver 8.5.31, to Serve Spring Webflux REST services.
One of those REST services calls to another, external REST Webservice.
public Mono<ServerResponse> select(ServerRequest request) {
return request.principal().cast(Authentication.class)
.flatMap(principal ->
client.get().uri(f -> buildUri(request, principal, request.queryParams(), f))
.exchange())
.flatMap((ClientResponse mapper) ->
ServerResponse.status(mapper.statusCode())
.headers(c -> mapper.headers().asHttpHeaders().forEach(c::put))
.body(mapper.bodyToFlux(DataBuffer.class)
.delayElements(Duration.ofSeconds(10))
.doOnCancel(() -> log.error("Cancelled client"))
.doOnTerminate(() -> log.error("Terminated client")), DataBuffer.class))
.doOnTerminate(() -> log.error("Termination called"));
}
If a browser calls my REST-Service, and after a short while cancels the connection, I can see the outer "Termination called" event, and that the client was terminated also. But the client termination seems to trigger an error in tomcat:
2018-07-25 12:50:42.860 DEBUG 12084 --- [ elastic-3] org.example.search.security.UserManager : Authorizing org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken#809aec11: Principal: cn=dv dbsearch client, ou=dbsearch, o=example, l=eb, st=unknown, c=de; Credentials: [PROTECTED]; Authenticated: false; Details: null; Not granted any authorities
2018-07-25 12:50:42.864 DEBUG 12084 --- [ elastic-3] org.example.search.security.UserManager : Successfully authorized: org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken#c03925ec: Principal: org.springframework.security.core.userdetails.User#809aec0e: Username: cn=dv dbsearch client, ou=dbsearch, o=example, l=eb, st=unknown, c=de; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ADMIN
2018-07-25 12:50:45.470 ERROR 12084 --- [ctor-http-nio-4] c.d.s.s.h.SolrSelectRequestHandler : Termination called
2018-07-25 12:51:15.562 ERROR 12084 --- [ parallel-3] c.d.s.s.h.SolrSelectRequestHandler : Terminated client
2018-07-25 12:51:15.625 ERROR 12084 --- [nio-8443-exec-2] o.s.w.s.adapter.HttpWebHandlerAdapter : Unhandled failure: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen, response already set (status=200)
2018-07-25 12:51:15.628 WARN 12084 --- [nio-8443-exec-2] o.s.h.s.r.ServletHttpHandlerAdapter : Handling completed with error: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen
2018-07-25 12:51:15.652 ERROR 12084 --- [nio-8443-exec-2] o.a.catalina.connector.CoyoteAdapter : Exception while processing an asynchronous request
java.lang.IllegalStateException: Calling [asyncError()] is not valid for a request with Async state [DISPATCHING]
at org.apache.coyote.AsyncStateMachine.asyncError(AsyncStateMachine.java:424)
at org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:470)
at org.apache.coyote.Request.action(Request.java:431)
at org.apache.catalina.core.AsyncContextImpl.setErrorState(AsyncContextImpl.java:388)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:176)
at org.apache.coyote.AbstractProcessor.dispatch(AbstractProcessor.java:232)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Sorry for the german errormessages, it means "client abortet connection".
I don't really have a problem with this errormessage per se, it's just, that my buffers in spring's Webclient don't seem to be cleared up (the log I did not reproduce locally, so it has diferent timestamps):
2018-07-23 08:44:36.892 ERROR 22707 — [reactor-http-nio-5] io.netty.util.ResourceLeakDetector : LEAK: ByteBuf.release() was not called before it's garbage-collected. See http://netty.io/wiki/reference-counted-objects.html for more information.
Recent access records:
Created at:
io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:331) io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:185)
So here the question: How can I cleanly end the WebClient connection, when the request to my REST-Service is cancelled?

I can't really say for sure about that exception message, but I know Tomcat improved this in the 8.5.x generation. Which version are you using? If you can provide a consistent way to reproduce this with a minimal application, you could create a new issue in jira.spring.io on Spring Framework, or Tomcat itself if you managed to reproduce it without Spring (although it should be a hard one to reproduce).
Now about releasing DataBuffer instances - DataBuffer instances can be pooled, depending on the implementation. Here the WebClient is using Netty, which is pooling buffers. So they need to be released when they're no longer used.
Looking at your implementation, I think those unreleased buffers come from this:
the WebClient is fetching data from the remote endpoint and creating DataBuffer instances
various Reactor operators along the way are buffering those using internal queues (depending on the prefetching and the operators used, the amount of queued buffers can vary)
when the subscriber fails or cancels, those buffers sitting in internal queues are not released as they should.
Currently Reactor does not offer a hook point to reach those objects in those error cases. But this is a brand new feature that's been added in Reactor core 3.2.0. This will be leveraged internally by Spring Framework with SPR-17025. Please follow this issue - your use case might be handy when it comes to testing the fix.

Related

rSocket websocket postman testing mime types and endpoints

I am using spring-boot-starter-webflux and spring-boot-starter-rsocket version 2.7.1
The rSocket transport is set to websocket like this:
spring.rsocket.server.transport=websocket
spring.rsocket.server.mapping-path=/rsocket
# this setting has no effect when transport==WEBSOCKET
spring.rsocket.server.port=7000
There's a spring #Controller endpoint #MessageMapping setup for a simple string like:
#MessageMapping("test")
String test() {
Logs.Info("*** Received test ***");
return "tested";
}
I want to get a successful test done with Postman. Run the spring boot app locally and connect to ws://localhost:7000 using mime types
dataMimeType: 'application/json'
metadataMimeType: 'message/x.rsocket.routing.v0'
Like this:
The rsocket websocket connects, but I can't hit the endpoint test
With error 1005 No Status Received: Missing status code even though one was expected
On the server the error is
DEBUG [reactor-http-nio-2] debug: [c4e97d34-1, L:/127.0.0.1:7000 - R:/127.0.0.1:2051] Cancelling Websocket inbound. Closing Websocket
DEBUG [reactor-http-nio-2] debug: [c4e97d34, L:/127.0.0.1:7000 - R:/127.0.0.1:2051] Removed handler: PongHandler, pipeline: DefaultChannelPipeline{(wsencoder = io.netty.handler.codec.http.websocketx.WebSocket13FrameEncoder), (wsdecoder = io.netty.handler.codec.http.websocketx.WebSocket13FrameDecoder), (reactor.right.reactiveBridge = reactor.netty.channel.ChannelOperationsHandler)}
DEBUG [reactor-http-nio-2] debug: [c4e97d34, L:/127.0.0.1:7000 ! R:/127.0.0.1:2051] An outbound error could not be processed
java.nio.channels.ClosedChannelException
at reactor.core.publisher.MonoErrorSupplied.call(MonoErrorSupplied.java:61)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:228)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
at reactor.core.publisher.SinkEmptyMulticast$VoidInner.complete(SinkEmptyMulticast.java:238)
at reactor.core.publisher.SinkEmptyMulticast.tryEmitEmpty(SinkEmptyMulticast.java:70)
at reactor.core.publisher.SinkEmptySerialized.tryEmitEmpty(SinkEmptySerialized.java:46)
What's the incorrect setting in Postman?
The answer is don't use postman. Rsocket is a binary protocol, Even though based on Websocket, There are many tools test it.
use spring message write a unit test
#Autowired
private RSocketRequester rSocketRequester;
StepVerifier.create(rSocketRequester
.route("test")
.retrieveMono(String.class))
.expectNext("tested")
.verifyComplete();
RSocket Client CLI (RSC)
rsc --request --route=test --debug ws://localhost:7000/rsocket
Actually the following message was received:
{
"data":"test",
"metadata":4
}
Per screenshot
But now the error on the server side is:
DEBUG [reactor-http-nio-6] lambda$receive$0: receiving ->
Frame => Stream ID: 2064452128 Type: REQUEST_N Flags: 0b100000 Length: 42
RequestN: 539124833
Data:
DEBUG [reactor-http-nio-6] sendErrorAndClose: sending -> InvalidSetupException: SETUP or RESUME frame must be received before any others

Break in fabric8 kubernetes client mock server

We were using fabric8 kubernetes client 5.3.x for watcher and it worked fine. Recently when we moved to 5.11.2 there were many changes observed and eventually the JUnit Tests started failing.
We use io.fabric8.kubernetes.client.server.mock.KubernetesServer
Earlier we were using ContainerStatus .withNewReady which now seem to be removed.
And then we added the following annotation
#Rule
public KubernetesServer myMockServer = new KubernetesServer(false, true);
After this, we are getting the following logs stating unsupported label requirement while the application code is sending this label.
[2022-02-03T07:30:22.733Z] 07:30:17.812 [pool-1-thread-1] DEBUG io.fabric8.kubernetes.client.dsl.internal.AbstractWatchManager - Watching http://localhost:40033/api/v1/namespaces/test/pods?labelSelector=app.kubernetes.io%2Fname%20in%20%28apps%29&timeoutSeconds=0&allowWatchBookmarks=true&watch=true...
[2022-02-03T07:30:22.733Z] 07:30:17.814 [MockWebServer /127.0.0.1:49882] WARN io.fabric8.kubernetes.client.server.mock.KubernetesAttributesExtractor - Ignoring unsupported label requirement: app.kubernetes.io/name in (apps)
[2022-02-03T07:30:22.733Z] 07:30:17.815 [MockWebServer /127.0.0.1:49882] DEBUG io.fabric8.kubernetes.client.server.mock.KubernetesAttributesExtractor - fromPath /api/v1/namespaces/test/pods?labelSelector=app.kubernetes.io%2Fname%20in%20%28apps%29&timeoutSeconds=0&allowWatchBookmarks=true&watch=true : {attributes: {namespace={key:namespace, value:test}, version={key:version, value:v1}, plural={key:plural, value:pods}}}
[2022-02-03T07:30:22.733Z] 07:30:17.815 [OkHttp http://localhost:40033/...] DEBUG io.fabric8.kubernetes.client.dsl.internal.WatcherWebSocketListener - WebSocket successfully opened
[2022-02-03T07:30:22.733Z] 07:30:20.818 [OkHttp http://localhost:40033/...] DEBUG io.fabric8.kubernetes.client.dsl.internal.AbstractWatchManager - Scheduling reconnect task
Is there something more should be done? Tests are in ERROR state and not Failed.
Sample JUnit program
public void testAddNewPodWatchEvent()
{
//given
doReturn(myClientMock).when(myTestObj).getClient();
doReturn(myWatcherSpy).when(myTestObj).createEventHandler();
String PATH =
"/api/v1/namespaces/test/pods?labelSelector=app.kubernetes.io%2Fname%20in%20%28apps%29&timeoutSeconds=0&watch=true";
Map<String, String> mockLabelMap = new HashMap<>();
mockLabelMap.put("foo", "testlabel");
mockLabelMap.put("app.kubernetes.io/name", "apps");
Pod accPod = createAppsPod(mockLabelMap, true);
myMockServer.expect()
.get()
.withPath(PATH)
.andUpgradeToWebSocket()
.open()
.waitFor(100)
.andEmit(new WatchEvent(accPod, "ADDED"))
.done()
.once();
//when
myTestObj.activate(mockProps);
sleepForWatchToBeInvoked();
//then
verify(myWatcherSpy, atLeastOnce()).eventReceived(Action.ADDED, accPod);
}

Oauth2 login with Apple ID returns request without CSRF token

I am trying to implement login with Apple ID in a Spring Boot application. I have it currently working so that I get the Apple login screen, and can authorize access. However, when Apple redirects back to https://example.com/myapp/login/oauth2/apple, I get the following error in my log :
2020-03-10 09:41:32.574 DEBUG 13644 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : /login/oauth2/code/apple at position 3 of 16 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2020-03-10 09:41:32.574 DEBUG 13644 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy : /login/oauth2/code/apple at position 4 of 16 in additional filter chain; firing Filter: 'CsrfFilter'
2020-03-10 09:41:32.575 DEBUG 13644 --- [nio-8080-exec-5] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for https://example.com/myapp/login/oauth2/code/apple
How can I make this work properly, without disabling CSRF facilities?
The problem is in the samesite=lax in 'session' cookie. Because of that, previous session related to login is being blocked by browser and new session is being created after Apple redirects to redirectUrl. This happens to all POST requests, since it is security issue and browsers drop cross-domain cookies. In order to resolve this issue, I disabled samesite in chrome and added the following config to my backend:
#Configuration
public class CookieConfiguration {
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("none");
serializer.setUseSecureCookie(true);
return serializer;
}
}
And my csrf config is as following:
.csrf(c -> c
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers("/login/oauth2/code/apple")
)
At least, this gives me a chance to test my code. I will keep updating if I find how to resolve this in production.

Spring websocket EOFException

My project is using spring-boot web socket and embedded tomcat to implement chat server. Everything is ok but sometimes I got EOFException, and then the client cannot send a message to chat server until I restart tomcat then everything worked ok. I don't know when EOFException will happen. Pls help me
[TRACE] 2017-10-23 06:17:10.707 [http-nio-7755-exec-4]
NativeWebSocketSession - Sending TextMessage payload=[{"result":..],
byteCount=164, last=true], StandardWebSocketSession[id=42b, uri=/chat]
[DEBUG] 2017-10-23 06:17:29.670 [http-nio-7755-exec-8]
LoggingWebSocketHandlerDecorator - Transport error in
StandardWebSocketSession[id=42b, uri=/chat] java.io.EOFException: null
at
org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1242)
~[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1182)
~[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:72)
~[tomcat-embed-websocket-8.5.16.jar!/:8.5.16] at
org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171)
~[tomcat-embed-websocket-8.5.16.jar!/:8.5.16] at
org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151)
~[tomcat-embed-websocket-8.5.16.jar!/:8.5.16] at
org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148)
[tomcat-embed-websocket-8.5.16.jar!/:8.5.16] at
org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[?:1.8.0_131] at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[?:1.8.0_131] at
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
[tomcat-embed-core-8.5.16.jar!/:8.5.16] at
java.lang.Thread.run(Thread.java:748) [?:1.8.0_131] [DEBUG] 2017-10-23
06:17:29.671 [http-nio-7755-exec-8] LoggingWebSocketHandlerDecorator -
StandardWebSocketSession[id=42b, uri=/chat] closed with
CloseStatus[code=1006, reason=null]
Yup, i solved it. This exception will happen after client or server was interrupted or stopped without calling close socket method(maybe lost internet, or shutdown laptop or mobile when using socket). So if we would like to solve this problem, we have to implement ping/pong mechanism, after interval if we cannot get pong response from client, we will close this socket. Another way, we can catch this exception, then we will close old socket.
Thanks,
Andy
Well, I couldn't follow that. So here's my solution.
I was getting mysterious EOFExceptions when user sessions ended, and my solution was to beef up error handling (use a more detailed onError function) in the websocket onError method, like this.
I credit my rooting and rutting around javacodegeeks for eventually pointed me in the right direction.
#OnError
public void onError(Session sess, Throwable e) {
Throwable cause = e.getCause();
/* normal handling... */
if (cause != null)
System.out.println("Error-info: cause->" + cause);
try {
// Likely EOF (i.e. user killed session)
// so just Close the input stream as instructed
sess.close();
} catch (IOException ex) {
System.out.println("Handling eof, A cascading IOException was caught: " + ex.getMessage());
ex.printStackTrace();
} finally {
System.out.println("Session error handled. (likely unexpected EOF) resulting in closing User Session.");
}
}

Grails validate runtime error

I use grails validate() function in my very simple update action inside a controller. The problem is very starnge. validate() is not executed at all and giving no error and stopping the execution. I am using Grails 2.3.3
Only the log gives the following error:
Runtime error executing action
Here is my controller code:
def update() {
println(params);
def study = Study.findByUid(params.uid);
study.description = params.description;
println(study); //study is found and printed
study.validate();
println("here"); //not executed and code below is also not executed
if(study.hasErrors()){
study.errors.each{
println it
}
render 'not saved!'
}
if (!study.save()) {
println("Error");
withFormat renderInternalError
}
else {
render "OK"
}
}
Stacktrace:
Request received for '/study/update':
org.apache.catalina.connector.RequestFacade#1eaba95c
servletPath:/study/update
pathInfo:null
Security filter chain: [
SecurityContextPersistenceFilter
MutableLogoutFilter
RequestHolderAuthenticationFilter
SecurityContextHolderAwareRequestFilter
GrailsRememberMeAuthenticationFilter
GrailsAnonymousAuthenticationFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
************************************************************
2013-11-27 11:56:04,738 [http-bio-8080-exec-9] DEBUG filter.GrailsRememberMeAuthenticationFilter - SecurityContextHolder not populated with remember-me token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken#f6816ea3: Principal: grails.plugin.springsecurity.userdetails.GrailsUser#b29dace9: Username: creator#example.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#1c07a: RemoteIpAddress: 127.0.0.1; SessionId: A1813ED8541AAC773CEF349475DB24A2; Granted Authorities: ROLE_USER'
2013-11-27 11:56:04,738 [http-bio-8080-exec-9] DEBUG filter.GrailsAnonymousAuthenticationFilter - SecurityContextHolder not populated with anonymous token, as it already contained: '{0}'
2013-11-27 11:56:04,740 [http-bio-8080-exec-9] TRACE intercept.AnnotationFilterInvocationDefinition - new candidate for '{0}': '{1}':{2}
2013-11-27 11:56:04,740 [http-bio-8080-exec-9] TRACE intercept.AnnotationFilterInvocationDefinition - config for '{0}' is '{1}':{2}
2013-11-27 11:56:04,741 [http-bio-8080-exec-9] TRACE core.StandardWrapper - Returning non-STM instance
2013-11-27 11:56:04,743 [http-bio-8080-exec-9] DEBUG simple.MemoryPageFragmentCachingFilter - No cacheable annotation found for POST:/hdspro/grails/study/update.dispatch [controller=study, action=update]
2013-11-27 11:56:05,651 [http-bio-8080-exec-9] DEBUG errors.GrailsExceptionResolver - Resolving exception from handler [org.codehaus.groovy.grails.web.servlet.mvc.SimpleGrailsController#98f1784]: org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException: Executing action [update] of controller [com.digithurst.hdspro.StudyController] caused exception: Runtime error executing action
2013-11-27 11:56:05,651 [http-bio-8080-exec-9] DEBUG errors.GrailsExceptionResolver - Resolving to view '/error' for exception of type [org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException], based on exception mapping [java.lang.Exception]
2013-11-27 11:56:05,651 [http-bio-8080-exec-9] DEBUG errors.GrailsExceptionResolver - Exposing Exception as model attribute 'exception'
2013-11-27 11:56:05,951 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver - StackOverflowError occurred when processing request: [POST] /hdspro/study/update - parameters:
acessionNumber: 3
uid: 3
description: Elbogen123
Stacktrace follows:
org.codehaus.groovy.grails.web.servlet.mvc.exceptions.ControllerExecutionException: Executing action [update] of controller [com.digithurst.hdspro.StudyController] caused exception: Runtime error executing action
at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:200)
at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63)
at grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter.doFilter(GrailsAnonymousAuthenticationFilter.java:53)
at grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter.doFilter(RequestHolderAuthenticationFilter.java:49)
at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.java:82)
at grails.plugin.springsecurity.web.filter.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:102)
at grails.plugin.springsecurity.web.filter.DebugFilter.doFilter(DebugFilter.java:69)
......
/
Study class:
class Study extends Document {
String uid
String description
String accessionNumber
Date date
String toString(){
return "Study" + uid + ": " + description;
}
}
From the comments I've read, you say you did not add any constraints to the domain class. From what I know validate() uses these constraints to check if your instance has errors. Ex:
class CartType {
String name
static constraints = {
name blank: false, nullable:false, maxSize: 50
}
String toString(){name}
}
In this example class we have a cartType. When we call validate() grails will check the constraint block. In this case it will check that the name is not left blank in the form, it cannot be null and that it's max size is 50 characters.
Note: The constraints block can also help grails build the database.
If you did not declare any contraints validate() will not have anything to check against. You will also most likely get an error when a value is null, because, by default, nullable is set to false.

Resources