I'm working with a Grails 2 application that is deployed to Amazon AWS which uses a software load balancer (ELB). The problem we are having is that the grails application instances are added to the load balancer before the application is fully initialized. It's the resources plugin specifically which serves static assets like javascript, css, images, etc.
The load balancer makes an http request to a 'healthcheck' URL.
GET '/myapp/lbcheck'
LoadBalancerController.groovy:
package myapp
class LoadBalancerController {
def healthService
def healthcheck() {
response.contentType = 'text/plain'
try {
healthService.checkDatabase()
render(status: 200, text: "Up")
}
catch(Exception ex) {
log.error("Error with database healthcheck " + ex)
render(status: 503, text: "Down")
}
}
}
HealthSerivce.groovy
package myapp
import groovy.sql.Sql
class HealthService {
def dataSource
// Either returns true, or throws an Exception
def checkDatabase() {
Sql sql = new Sql(dataSource)
sql.rows("SELECT 429")
sql.close()
return true
}
}
The SQL query clearly isn't enough. It seems like we need to check some other kind of property in the framework to determine that it has been initialized.
You could try setting setting a field on your healthService bean to true inside of BootStrap.groovy. I think that's run after Grails is fully initialized.
package myapp
import groovy.sql.Sql
class HealthService {
def dataSource
def initializationComplete = false
// Either returns true, or throws an Exception
def checkDatabase() {
Sql sql = new Sql(dataSource)
sql.rows("SELECT 429")
sql.close()
return true
}
}
inside BootStrap.groovy:
class BootStrap {
def healthService
def init = { servletContext ->
healthService.initializationComplete = true
}
}
in your LoadBalancerController.groovy:
def healthcheck() {
response.contentType = 'text/plain'
def healthy = false
try {
healthy = healthService.with {
initializationComplete && checkDatabase()
}
}
catch(Exception ex) {
log.error("Error with database healthcheck " + ex)
}
if (healthy) {
render(status: 200, text: "Up")
} else {
render(status: 503, text: "Down")
}
}
Related
Every time I start my batch job it throws an IllegalStateException and says it detected a transaction in JobRepository. I did some research and removed all #Transactional annotations in my code.
I use the Grails Spring Batch Plugin you can find here, and I work with Grails 2.3.11 and Java 8. My code looks like this:
SimpleJobBatchConfig.groovy
beans {
xmlns batch:"http://www.springframework.org/schema/batch"
batch.job(id: 'simpleJob') {
batch.step(id: 'printStep') {
batch.tasklet(ref: 'printHelloWorld')
}
}
printHelloWorld(SimpleJobTasklet) { bean ->
bean.autowire = 'byName'
}
}
BatchTestController.groovy
class BatchelorController {
def batchTestService
def index() {
}
def launchSimpleJob() {
batchTestService.launchSimpleJob()
}
}
BatchTestService.groovy
class BatchTestService {
def springBatchService
def launchSimpleJob() {
springBatchService.launch("simpleJob")
}
}
SimpleJobTasklet.groovy
class SimpleJobTasklet implements Tasklet {
#Override
RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
println("Hello World!")
return RepeatStatus.FINISHED
}
}
Grails services are transactional by default. You can customize the settings for the whole class or per-method with #Transactional but if you have no annotations it's the same as having a class-scope Spring #Transactional annotation.
To make your service non-transactional, add static transactional = false, e.g.
class BatchTestService {
static transactional = false
def springBatchService
...
}
}
Consider this real world example, which is code written to make 'GET' and 'POST' calls to the same REST service endpoint via the Rest Client Builder (Grails plugin). I dislike the duplication in that the header and content type setup is identical, but am not sure how to refactor the common pieces out given that they are calling methods on the closure that is being passed to the get() or post() method call. Please provide a concrete example of a good refactoring out of the duplication in your answer.
private def doGetCall(String endpoint, def config) {
def response = new RestBuilder().get(config.baseURI+endpoint) {
contentType("application/json")
header("Authorization", "ApiKey " + config.base64EncodedApiKey)
header("ClientId", config.clientId)
}
handleResponse(response, config, endpoint);
return response;
}
private def doPostCall(String endpoint, def payload, def config) {
def response = new RestBuilder().post(config.baseURI+endpoint) {
contentType("application/json")
header("Authorization", "ApiKey " + config.base64EncodedApiKey)
header("ClientId", config.clientId)
json(payload)
}
handleResponse(response, config, endpoint, payload)
return response;
}
Groovy 1.8 added Closure composition, so if you're using a version of Grails which uses Groovy 1.8 or greater:
private def doGetCall(String endpoint, def config) {
def response = new RestBuilder().get(config.baseURI+endpoint, composeRequest(config))
handleResponse(response, config, endpoint);
return response;
}
private def doPostCall(String endpoint, def payload, def config) {
def response = new RestBuilder().post(config.baseURI+endpoint, composeRequest(config, { json(payload) }))
handleResponse(response, config, endpoint, payload)
return response;
}
private def composeRequest(def config, Closure clos = null) {
def request = {
contentType("application/json")
header("Authorization", "ApiKey " + config.base64EncodedApiKey)
header("ClientId", config.clientId)
}
if (clos != null) {
request = request << clos
}
request
}
Will this suffice?
class RestTestService {
def rest
def methodMissing(String name, args) {
if( !( name in ['get', 'post'] ) ) { // can add PUT & DELETE in future
// throw missing method exception for method names other than above
throw new MissingMethodException(
"Http Method $name does not exist or not yet implemented.")
}
def (endpoint, config, payload) = args?.toList()
def response = rest."$name"(config.baseURI + endpoint) {
contentType( "application/json" )
header("Authorization", "ApiKey " + config.base64EncodedApiKey )
header( "ClientId", config.clientId )
if ( name == 'post' && payload ) {
json( payload )
}
}
handleResponse(response, config, endpoint)
return response
}
private void handleResponse(def response, def config, def endpoint) { ... }
public def doGetCall(String endpoint, def config) {
get( endpoint, config )
}
public def doPostCall(String endpoint, def payload, def config) {
post( endpoint, config, payload )
}
}
//resources.groovy
beans = {
rest(grails.plugins.rest.client.RestBuilder)
}
Above makes use of methodMissing in order to decide which http method to call during runtime.
Also note, instead of creating RestBuilder for each http call, I suggest to use it as a bean as shown above in resources.groovy and inject it to the class when it is used. If it were a grails artifact (controller, service) then it will be autowired otherwise bean rest has to be wired appropriately.
You might abstract by using doGetCall and doPostCall or totally remove them if required.
I am using grails 2.1.0. I have installed spring-security-core plugin.
When I create user it is creating it. But when I try to login then it shows:
"Sorry, we were not able to find a user with that username and password."
And there is also another fact that is when I use the same password for different user it does not save password with similar encoded value like for user 1 I have used password 123 which is saved in database like this
d535ce213a0e8e4f9e724af47c46eea409ef401c03617b749da618a82890d743
and for user 2 I also used password 123 and this time it is saved like this
0849ea79a2c1bca057ded06c3053fb5bc5d7ba52b50982e73e44894d4f3e0aa6
I don't understand. Can anyone please help me on this ?
my config.groovy >>>
// locations to search for config files that get merged into the main config;
// config files can be ConfigSlurper scripts, Java properties files, or classes
// in the classpath in ConfigSlurper format
// grails.config.locations = [ "classpath:${appName}-config.properties",
// "classpath:${appName}-config.groovy",
// "file:${userHome}/.grails/${appName}-config.properties",
// "file:${userHome}/.grails/${appName}-config.groovy"]
// if (System.properties["${appName}.config.location"]) {
// grails.config.locations << "file:" + System.properties["${appName}.config.location"]
// }
grails.project.groupId = appName // change this to alter the default package name and Maven publishing destination
grails.mime.file.extensions = true // enables the parsing of file extensions from URLs into the request format
grails.mime.use.accept.header = false
grails.mime.types = [
all: '*/*',
atom: 'application/atom+xml',
css: 'text/css',
csv: 'text/csv',
form: 'application/x-www-form-urlencoded',
html: ['text/html','application/xhtml+xml'],
js: 'text/javascript',
json: ['application/json', 'text/json'],
multipartForm: 'multipart/form-data',
rss: 'application/rss+xml',
text: 'text/plain',
xml: ['text/xml', 'application/xml']
]
// URL Mapping Cache Max Size, defaults to 5000
//grails.urlmapping.cache.maxsize = 1000
// What URL patterns should be processed by the resources plugin
grails.resources.adhoc.patterns = ['/images/*', '/css/*', '/js/*', '/plugins/*']
// The default codec used to encode data with ${}
grails.views.default.codec = "none" // none, html, base64
grails.views.gsp.encoding = "UTF-8"
grails.converters.encoding = "UTF-8"
// enable Sitemesh preprocessing of GSP pages
grails.views.gsp.sitemesh.preprocess = true
// scaffolding templates configuration
grails.scaffolding.templates.domainSuffix = 'Instance'
// Set to false to use the new Grails 1.2 JSONBuilder in the render method
grails.json.legacy.builder = false
// enabled native2ascii conversion of i18n properties files
grails.enable.native2ascii = true
// packages to include in Spring bean scanning
grails.spring.bean.packages = []
// whether to disable processing of multi part requests
grails.web.disable.multipart=false
// request parameters to mask when logging exceptions
grails.exceptionresolver.params.exclude = ['password']
// configure auto-caching of queries by default (if false you can cache individual queries with 'cache: true')
grails.hibernate.cache.queries = false
environments {
development {
grails.logging.jul.usebridge = true
}
production {
grails.logging.jul.usebridge = false
// TODO: grails.serverURL = "http://www.changeme.com"
}
}
// log4j configuration
log4j = {
// Example of changing the log pattern for the default console appender:
//
//appenders {
// console name:'stdout', layout:pattern(conversionPattern: '%c{2} %m%n')
//}
error 'org.codehaus.groovy.grails.web.servlet', // controllers
'org.codehaus.groovy.grails.web.pages', // GSP
'org.codehaus.groovy.grails.web.sitemesh', // layouts
'org.codehaus.groovy.grails.web.mapping.filter', // URL mapping
'org.codehaus.groovy.grails.web.mapping', // URL mapping
'org.codehaus.groovy.grails.commons', // core / classloading
'org.codehaus.groovy.grails.plugins', // plugins
'org.codehaus.groovy.grails.orm.hibernate', // hibernate integration
'org.springframework',
'org.hibernate',
'net.sf.ehcache.hibernate'
}
// Added by the Spring Security Core plugin:
grails.plugins.springsecurity.userLookup.userDomainClassName = 'common.auth.User'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'common.auth.UserAuthority'
grails.plugins.springsecurity.authority.className = 'common.auth.Authority'
my login controller >>>
import grails.converters.JSON
import javax.servlet.http.HttpServletResponse
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.security.authentication.AccountExpiredException
import org.springframework.security.authentication.CredentialsExpiredException
import org.springframework.security.authentication.DisabledException
import org.springframework.security.authentication.LockedException
import org.springframework.security.core.context.SecurityContextHolder as SCH
import org.springframework.security.web.WebAttributes
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
class LoginController {
/**
* Dependency injection for the authenticationTrustResolver.
*/
def authenticationTrustResolver
/**
* Dependency injection for the springSecurityService.
*/
def springSecurityService
/**
* Default action; redirects to 'defaultTargetUrl' if logged in, /login/auth otherwise.
*/
def index = {
if (springSecurityService.isLoggedIn()) {
redirect uri: SpringSecurityUtils.securityConfig.successHandler.defaultTargetUrl
} else {
redirect action: 'auth', params: params
}
}
/**
* Show the login page.
*/
def auth = {
def config = SpringSecurityUtils.securityConfig
if (springSecurityService.isLoggedIn()) {
redirect uri: config.successHandler.defaultTargetUrl
return
}
String view = 'auth'
String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
render view: view, model: [postUrl: postUrl,
rememberMeParameter: config.rememberMe.parameter]
}
/**
* The redirect action for Ajax requests.
*/
def authAjax = {
response.setHeader 'Location', SpringSecurityUtils.securityConfig.auth.ajaxLoginFormUrl
response.sendError HttpServletResponse.SC_UNAUTHORIZED
}
/**
* Show denied page.
*/
def denied = {
if (springSecurityService.isLoggedIn() &&
authenticationTrustResolver.isRememberMe(SCH.context?.authentication)) {
// have cookie but the page is guarded with IS_AUTHENTICATED_FULLY
redirect action: 'full', params: params
}
}
/**
* Login page for users with a remember-me cookie but accessing a IS_AUTHENTICATED_FULLY page.
*/
def full = {
def config = SpringSecurityUtils.securityConfig
render view: 'auth', params: params,
model: [hasCookie: authenticationTrustResolver.isRememberMe(SCH.context?.authentication),
postUrl: "${request.contextPath}${config.apf.filterProcessesUrl}"]
}
/**
* Callback after a failed login. Redirects to the auth page with a warning message.
*/
def authfail = {
def username = session[UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY]
String msg = ''
def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
if (exception) {
if (exception instanceof AccountExpiredException) {
msg = g.message(code: "springSecurity.errors.login.expired")
} else if (exception instanceof CredentialsExpiredException) {
msg = g.message(code: "springSecurity.errors.login.passwordExpired")
} else if (exception instanceof DisabledException) {
msg = g.message(code: "springSecurity.errors.login.disabled")
} else if (exception instanceof LockedException) {
msg = g.message(code: "springSecurity.errors.login.locked")
} else {
msg = g.message(code: "springSecurity.errors.login.fail")
}
}
if (springSecurityService.isAjax(request)) {
render([error: msg] as JSON)
} else {
flash.message = msg
redirect action: 'auth', params: params
}
}
/**
* The Ajax success redirect url.
*/
def ajaxSuccess = {
render([success: true, username: springSecurityService.authentication.name] as JSON)
}
/**
* The Ajax denied redirect url.
*/
def ajaxDenied = {
render([error: 'access denied'] as JSON)
}
}
my authority.groovy >>>
package common.auth
class Authority {
String authority
static mapping = {
cache true
}
static constraints = {
authority blank: false, unique: true
}
}
my user domain.groovy where my user will be saved >>>
package common.auth
class User {
transient springSecurityService
String realname
String username
String password
String designation
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static constraints = {
username blank: false, unique: true
password blank: false
}
static mapping = {
password column: '`password`'
}
Set<Authority> getAuthorities() {
UserAuthority.findAllByUser(this).collect { it.authority } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
my userauthority.groovy >>>
package common.auth
import org.apache.commons.lang.builder.HashCodeBuilder
class UserAuthority implements Serializable {
User user
Authority authority
boolean equals(other) {
if (!(other instanceof UserAuthority)) {
return false
}
other.user?.id == user?.id &&
other.authority?.id == authority?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (authority) builder.append(authority.id)
builder.toHashCode()
}
static UserAuthority get(long userId, long authorityId) {
find 'from UserAuthority where user.id=:userId and authority.id=:authorityId',
[userId: userId, authorityId: authorityId]
}
static UserAuthority create(User user, Authority authority, boolean flush = false) {
new UserAuthority(user: user, authority: authority).save(flush: flush, insert: true)
}
static boolean remove(User user, Authority authority, boolean flush = false) {
UserAuthority instance = UserAuthority.findByUserAndAuthority(user, authority)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(User user) {
executeUpdate 'DELETE FROM UserAuthority WHERE user=:user', [user: user]
}
static void removeAll(Authority authority) {
executeUpdate 'DELETE FROM UserAuthority WHERE authority=:authority', [authority: authority]
}
static mapping = {
id composite: ['authority', 'user']
version false
}
}
And my createUser action to create user in AdministratorActionController >>>
package administrator
import common.auth.User
class AdmistratorActionController {
def springSecurityService
def index() {
redirect(controller: 'admistratorAction', action: 'createUser')
}
def createUser = {
User user = new User(params)
def password = user.password
def salt = user.username //depends on what you're using as a salt
user.password = springSecurityService.encodePassword(password, salt)
user.save()
flash.message = "User Create Successfully !!!"
}
}
I think you are encoding your password twice, you already have encode() on beforeInsert in the domain, I think you need not to encode it again.
Spring Security use hash of user password + a salt. Salt is used to defeat pre-computed rainbow table attacks that could otherwise be used to greatly improve the efficiency of cracking the hashed password database. See http://en.wikipedia.org/wiki/Salt_(cryptography)
For example, if we're using username as a hash, real value in database will be:
md5(user.password + '|' + user.username)
So for two different userw with same password:
user: username: 'user1', password: 123
user: username: 'user2', password: 123
you'll get two different values in database:
md5('user1|123') == 975204d0650cc642730866d56f66b6fb
md5('user2|123') == aa12022115555842a7f80564940ae49d
So, if hacker have access to you database he cannot guess the password.
Spring Security uses same hashing function and salt for saving and loading user. And if it unable to find user it probably means that you've used different salt for initial save and for later load from the database. Make sure that you have same salt source, and it's not changing (like id field, that have null value when you just creating a new user)
Are there in any idioms in grails which help us with saving domain objects ?
For example
i may want to do something like
if(candidate.hasErrors || !candidate.save)
{
candidate.errors.each {
log it
}
However i do not want to spread the logic across all the places i do domainObject.save.
I also do not want seperate class like say repo to which I pass this domainObject and put in this logic
Thanks
Sudarshan
Here's a service method that I've used to validate and save, but log resolved validation messages on failure. It's helpful to use this instead of just println error or log.warn error since the toString() for error objects is very verbose and you just want to see what would be displayed on the GSP:
class MyService {
def messageSource
def saveOrUpdate(bean, flush = false) {
return validate(bean) ? bean.save(flush: flush) : null
}
boolean validate(bean) {
bean.validate()
if (bean.hasErrors()) {
if (log.isEnabledFor(Level.WARN)) {
def message = new StringBuilder(
"problem ${bean.id ? 'updating' : 'creating'} ${bean.getClass().simpleName}: $bean")
def locale = Locale.getDefault()
for (fieldErrors in bean.errors) {
for (error in fieldErrors.allErrors) {
message.append("\n\t")
message.append(messageSource.getMessage(error, locale))
}
}
log.warn message
}
bean.discard()
return false
}
return true
}
And here's an example in a controller:
class MyController {
def myService
def actionName = {
def thing = new Thing(params)
if (myService.saveOrUpdate(thing)) {
redirect action: 'show', id: thing.id
}
else {
render view: 'create', model: [thing: thing]
}
}
}
Edit: It's also possible to add these methods to the MetaClass, e.g. in BootStrap.groovy:
class BootStrap {
def grailsApplication
def messageSource
def init = { servletContext ->
for (dc in grailsApplication.domainClasses) {
dc.metaClass.saveOrUpdate = { boolean flush = false ->
validateWithWarnings() ? delegate.save(flush: flush) : null
}
dc.metaClass.validateWithWarnings = { ->
delegate.validate()
if (delegate.hasErrors()) {
def message = new StringBuilder(
"problem ${delegate.id ? 'updating' : 'creating'} ${delegate.getClass().simpleName}: $delegate")
def locale = Locale.getDefault()
for (fieldErrors in delegate.errors) {
for (error in fieldErrors.allErrors) {
message.append("\n\t")
message.append(messageSource.getMessage(error, locale))
}
}
log.warn message
delegate.discard()
return false
}
return true
}
}
}
}
This depends on a 'log' variable being in scope, which will be true in any Grails artifact. This changes the controller usage slightly:
class MyController {
def actionName = {
def thing = new Thing(params)
if (thing.saveOrUpdate()) {
redirect action: 'show', id: thing.id
}
else {
render view: 'create', model: [thing: thing]
}
}
}
As a metaclass method it may make more sense to rename it, e.g. saveWithWarnings().
I have following situation (simplified, of course):
MyDomain.groovy:
class MyDomain {
MyAnotherDomain anotherDomain // lazy loaded
}
MyService.groovy:
class MyService {
boolean transactional = true
def doSomething(id) {
// ... some code...
}
}
MYController.groovy:
class MyController {
def myService
def doAction = {
MyDomain aaa = ...GET_IT_FROM_SOMEWHERE...
try {
myService.doSomething(id)
} catch (RuntimeError e) {
flash.message = 'sorry.guy.your.transaction.was.rollbacked'
}
[myData: aaa]
}
}
doAction.gsp:
<html>
<body>
${myData.anotherDomain}
</body>
</html>
Problem happens when doSomething() throws RuntimeException. This RuntimeException rollback transaction and ends Hibernate session as well. When I render doAction.gsp after the RuntimeError, it ends with error, because lazy loaded field anotherDomain cannot be read (no session). Now you can say "ok, don't use RuntimeException", but I need the automatic transaction rollback.
Any ideas how to keep Hibernate session open even if RuntimeException happens in transactional service, so that lazy loading in gsp can render properly? Thanks.
If your Hibernate session is destroyed during rollback and xception throwing, you can try to manually reattach it to the current Hibernate session:
MyController.groovy:
class MyController {
def myService
def doAction = {
MyDomain aaa = ...GET_IT_FROM_SOMEWHERE...
try {
myService.doSomething(id)
} catch (RuntimeError e) {
flash.message = 'sorry.guy.your.transaction.was.rollbacked'
if(!aaa.isAttached()) {
aaa.attach()
}
}
[myData: aaa]
}
}
Hope that's suitable for your needs.
Reference