I am making both a Grails plugin and several Grails apps that will use the plugin.
I want to define a few properties and give them defaults in the plugin, but allow apps to override their values (optional). This question surrounds the mechanical details of how to wire both plugin and child app alike in this manner.
Say I want my plugin (grails-myplugin) to define the following properties in its Config.groovy:
myplugin {
fizz {
whistles = true // Default for all child apps using this plugin
buzz = 3 // Default for all child apps using this plugin
}
}
grails.plugins.anotherPlugin.widget = 'auto'
grails.plugins.anotherPlugin.foo = '${myplugin.fizz.buzz}-40' // Hence, by default, is '3-40'
Now, I would like a child Grails app, say, myapp.war (which uses grails-myplugin) to override the 2 fizz properties:
// myapp's Config.groovy:
myplugin {
fizz {
// whistles property not defined here so this app uses the
// plugin's default of "true"
// Overrides the plugin's value of 3; this imples
// grails.plugins.somePlugin.foo is '12-40'
buzz = 12
}
}
A few issues here:
Have I placed everything correctly for the desired functionality?
With the above configuration, in grails-myplugin's Config.groovy, I have an error message:
Multiple markers at this line: - The type groovy.lang.MetaClass cannot be resolved. It is indirectly referenced from required .class files. - The type groovy.lang.GroovyObject cannot be resolved. It is indirectly referenced from required .class files.
The fact that I'm getting this error tells me that I'm ether trying to do something that is impossible in Grails, or that I'm just doing it wrong. Ideas?
Here you have how the quartz plugin resolved it:
https://github.com/grails-plugins/grails-quartz/blob/master/QuartzGrailsPlugin.groovy
Take a look at loadQuartzConfig method.
Also be aware that grails plugin exclude some parts of it to avoid problems when installing. It's configured on the plugin file like this:
def pluginExcludes = [
'grails-app/jobs/**',
'src/docs/**',
'web-app/**'
]
Related
I am new to grails and while working with Spring Security LDAP plugin it was identified that it accepts the ldap server password in plain text only. The task in hand is to pass an encrypted password which is decrypted before it is consumed by the plugin during its initialization phase.
I have already searched for all possible blogs and stackoverflow questions but could not find a way to extend the main plugin class to simply override the doWithSpring() method so that i can simply add the required decryption logic for the Ldap server password. Any help here will be appreciated.
I have already seen and tried jasypt plugin but it also does not work well if the password is stored in some external file and not application yml. So I am looking for a solution to extend the Spring security plugin main class, add the required behavior and register the custom class.
EDIT
Adding the snippet from Grails LDAP Security plugin, which I am trying to override. So If i am successfully able to update the value of securityConfig object before the plugin loads, the purpose is solved.
Some snippet from the plugin:
def conf = SpringSecurityUtils.securityConfig
...
...
contextSource(DefaultSpringSecurityContextSource, conf.ldap.context.server) { // 'ldap://localhost:389'
authenticationSource = ref('ldapAuthenticationSource')
authenticationStrategy = ref('authenticationStrategy')
userDn = conf.ldap.context.managerDn // 'cn=admin,dc=example,dc=com'
**password = conf.ldap.context.managerPassword // 'secret'**
contextFactory = contextFactoryClass
dirObjectFactory = dirObjectFactoryClass
baseEnvironmentProperties = conf.ldap.context.baseEnvironmentProperties // none
cacheEnvironmentProperties = conf.ldap.context.cacheEnvironmentProperties // true
anonymousReadOnly = conf.ldap.context.anonymousReadOnly // false
referral = conf.ldap.context.referral // null
}
ldapAuthenticationSource(SimpleAuthenticationSource) {
principal = conf.ldap.context.managerDn // 'cn=admin,dc=example,dc=com'
**credentials = conf.ldap.context.managerPassword // 'secret'**
}
You don't need to override the doWithSpring() method in the existing plugin. You can provide your own plugin which loads after the one you want to affect and have your doWithSpring() add whatever you want to the context. If you add beans with the same name as the ones added by the other plugin, yours will replace the ones provided by the other plugin as long as you configure your plugin to load after the other one. Similarly, you could do the same think in resources.groovy of the app if you don't want to write a plugin for this.
You have other options too. You could write a bean post processor or bean definition post processor that affects the beans created by the other plugin. Depending on the particulars, that might be a better idea.
EDIT:
After seeing your comment below I created a simple example that shows how you might use a definition post processor. See the project at https://github.com/jeffbrown/postprocessordemo.
The interesting bits:
https://github.com/jeffbrown/postprocessordemo/blob/master/src/main/groovy/demo/SomeBean.groovy
package demo
class SomeBean {
String someValue
}
https://github.com/jeffbrown/postprocessordemo/blob/master/src/main/groovy/demo/SomePostProcessor.groovy
package demo
import org.springframework.beans.BeansException
import org.springframework.beans.MutablePropertyValues
import org.springframework.beans.PropertyValue
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
class SomePostProcessor implements BeanDefinitionRegistryPostProcessor{
#Override
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition definition = registry.getBeanDefinition('someBean')
MutablePropertyValues values = definition.getPropertyValues()
PropertyValue value = values.getPropertyValue('someValue')
def originalValue = value.getValue()
// this is where you could do your decrypting...
values.addPropertyValue('someValue', "MODIFIED: ${originalValue}".toString())
}
#Override
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
https://github.com/jeffbrown/postprocessordemo/blob/master/grails-app/conf/spring/resources.groovy
beans = {
someBean(demo.SomeBean) {
someValue = 'Some Value'
}
somePostProcessor demo.SomePostProcessor
}
https://github.com/jeffbrown/postprocessordemo/blob/master/grails-app/init/postprocessordemo/BootStrap.groovy
package postprocessordemo
import demo.SomeBean
class BootStrap {
SomeBean someBean
def init = { servletContext ->
log.info "The Value: ${someBean.someValue}"
}
def destroy = {
}
}
At application startup you will see log output that looks something like this...
2017-10-23 19:04:54.356 INFO --- [ main] postprocessordemo.BootStrap : The Value: MODIFIED: Some Value
The "MODIFIED" there is evidence that the bean definition post processor modified the property value in the bean. In my example I am simply prepending some text to the string. In your implementation you could decrypt a password or do whatever you want to do there.
I hope that helps.
After trying Jasypt plugin and BeanPostProcessor solutions unsuccessfully for my use case, I found below solution to work perfectly.
To describe again the problem statement here,
a) we had to keep the passwords in an encrypted format inside properties files
b) and given we were packaging as a war file so the properties must not be kept inside the war to allow automated deployment scripts update the encrypted passwords depending on the environment
Jasypt plugin was a perfect solution for the use case a), but it was not able to cover the b) scenario
Moreover, the Grails LDAP Security plugin was getting loaded quite early hence Bean Post processors were also not helping out here.
Solution:
Created a new class by implementing the interface SpringApplicationRunListener. Extended its methods and parsed the properties file using YamlPropertySourceLoader
Sample code:
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
PropertySource<?> applicationYamlPropertySource = loader.load(
"application.yml", new ClassPathResource("application.yml"),"default");
return applicationYamlPropertySource;
Once the properties were loaded inside the MapPropertySource object, parsed them for the encrypted values and applied the decryption logic.
This whole implementation was executed before any plugins were initialized during Grails bootup process solving the purpose.
Hope it will help others.
I'm attempting to add a very simple DelegatingFilterProxy to my Grails application via a custom plugin.
I would like the filter to execute after the springSecurityFilterChain filter so that I can make use of certain security-specific information in the custom filter.
I've tried using loadAfter and loadBefore with different techniques in doWithWebDescriptor, but all end with the same result: my custom filter-mapping is always listed before springSecurityFilterChain. Below is my current iteration of doWithWebDescriptor
def contextParams = xml.'context-param'
contextParams[contextParams.size() - 1] + {
filter {
'filter-name'('auditFilter')
'filter-class'(DelegatingFilterProxy.name)
}
}
def filterMappings = xml.'filter-mapping'
filterMappings[filterMappings.size() - 1] + {
'filter-mapping' {
'filter-name'('auditFilter')
'url-pattern'('/*')
}
}
Is there a correct way to go about this?
The grails WebXmlConfig plugin provides additional features to work with the web.xml file. It hooks into an event, eventWebXmlEnd that listens for when the web.xml file is done generating. Once it's done, the plugin will attempt to re-order the servlet filters if necessary. It iterates over all other plugins and looks for a webXmlFilterOrder property in the plugin descriptor. If it finds that property, it registers the desired position and after looping over all plugins, rewrites the web.xml file.
The plugin is actually already included as a dependency of the Spring Security Core plugin, but you should also add it to your own plugin's BuildConfig.groovy file:
compile ':webxml:1.4.1'
So to hook in this functionality, you need to add a webXmlFilterOrder property to your plugin descriptor (really just a getter actually). You can throw this right above the doWithWebDescriptor closure (youll also need to import grails.plugin.webxml.FilterManager):
def getWebXmlFilterOrder() {
[auditFilter: FilterManager.GRAILS_WEB_REQUEST_POSITION + 101]
}
The getter returns a Map where the key is the name of your filter and the value is an int describing the desired position in web.xml. WebXmlConfig provides a FilterManager class with a few position constants that you can use. In this case, GRAILS_WEB_REQUEST_POSITION has a value of 1000.
A quick look at the Spring Security Core plugin descriptor shows that it's putting the springSecurityFilterChain filter at FilterManager.GRAILS_WEB_REQUEST_POSITION + 100, so by setting your auditFilter to anything higher, it will appear below spring security in web.xml.
Sometimes I see, in a Grails app's Config.groovy, something like:
log4j = {
appenders {
...
}
root {
...
}
...etc.
}
This is clearly a programmatic way of specifying the log4j configs. I'm wondering:
Is it possible to specify the log4j configs in a log4j.xml file, and then tell the Grails app where to look for it?
If so, where should I place the log4j.xml inside the Grails app, and how/where do I "connect" it to the app?
Grails by default won't use log4j.xml. You have a few options.
Wire up Spring to use log4j.xml. This can be somewhat complex if you aren't familiar with Spring and the required beans. Which you and I already discussed.
Use the Log4j XML plugin for Grails.
For simple use cases the second option would be preferred.
Update
Based on your follow up question in the comments you might be better off configuring logging directly in your plugin using the DSL syntax. Burt explains how this is done in another post.
Grails automatically excludes any file in grails-app/conf that matches log4j.*:
From scripts/_GrailsWar.groovy:
ant.copy(todir:"${stagingDir}/WEB-INF/classes", failonerror:false, preservelastmodified:true) {
fileset(dir:"${basedir}/grails-app/conf") {
exclude(name:"*.groovy")
exclude(name:"log4j.*")
exclude(name:"**/hibernate/**")
exclude(name:"**/spring/**")
}
fileset(dir:"${basedir}/grails-app/conf/hibernate", includes:"**/**")
fileset(dir:"${grailsSettings.sourceDir}/java") {
include(name:"**/**")
exclude(name:"**/*.java")
}
fileset(dir:"${grailsSettings.sourceDir}/groovy") {
include(name:"**/**")
exclude(name:"**/*.groovy")
}
fileset(dir:"${resourcesDirPath}", includes:"log4j.properties")
}
As pointed out, there are other ways to achieve what you want without using the log4j.xml file, but if you do want to use it, you can add some logic to your grails-app/conf/BuildConfig.groovy to ensure that it gets packaged in your WAR:
grails.war.resources = { stagingDir, args ->
copy(todir:"${stagingDir}/WEB-INF/classes", failonerror:false, preservelastmodified:true) {
fileset(dir:"${basedir}/grails-app/conf") {
include(name:"log4j.xml")
}
}
}
I'm using Drools-Gorm plugin. A bit of code from this plugin:
Domain
class SessionInfoDomain implements SessionInfo {
...
Environment env
static transients = ['env']
...
}
Service
class GormDomainService {
def SessionInfo getNewSessionInfo(Environment env) {
return new SessionInfoDomain(env: env)
}
}
But this plugin doesn't work with Grails>2.0.2 because:
Grails 2.0.2 Data Binding Improvements see link
In Grails 2.0.2 the data binding mechanism will by default exclude all properties which are static, transient or dynamically typed, so we get SessionInfoDomain without env. We can add ( bindable:true ) in constraints of SessionInfoDomain.
My question: how to ease apply this plugin to my App without change plugin?
This article suggests Tomcat 7 apps should use a JDBC connection pool instead of a commons-dbcp connection pool. However, the latter is the default for a Grails app, and it's not obvious how to change it.
My guess is that I need to define a Spring bean in resources.groovy that overrides a bean that is normally created by default, but I've no idea what this bean should be named or what properties I need to set.
The easiest thing to do would probably be to use the jdbc-pool plugin. Since the configuration options for this pool are intentionally very similar to Commons DBCP (they're documented here) you can use the plugin to define the jar dependency and manage switching the class for you. The plugin hasn't been updated in a year so it's a little out of date (the plugin uses version 1.0.9.0 but the latest is 1.0.9.3) so you might want to define the plugin dependency excluding the jar, and add one for the newer version. It's in the ebr repo, so you'll need to add that to your BuildConfig.groovy (see the plugin's version for how he did it).
There are configuration notes for the pool here and a series of blog posts by the author here.
If you do want to configure this without using the plugin, add the ebr repo and the jar dependency to BuildConfig.groovy:
repositories {
inherits true
...
ebr()
}
dependencies {
runtime('org.apache.tomcat:com.springsource.org.apache.tomcat.jdbc:1.0.9.3') {
transitive = false
}
}
and create an override for the dataSource bean in resources.groovy:
import org.apache.tomcat.jdbc.pool.DataSource
beans = {
dataSource(DataSource) {
// mandatory
driverClassName = '${dataSource.driverClassName}'
username = '${dataSource.username}'
password = '${dataSource.password}'
url = '${dataSource.url}'
// optional
minEvictableIdleTimeMillis=1800000
timeBetweenEvictionRunsMillis=1800000
numTestsPerEvictionRun=3
testOnBorrow=true
testWhileIdle=true
testOnReturn=true
validationQuery="SELECT 1"
}
}
It's convenient to use single-quoted strings with ${} placeholders to take advantage of Spring's property placeholder functionality and keep things DRY since you've already set the driver and connect info in DataSource.groovy.
In DataSource.groovy I use the following:
environments {
integration {
dataSource {
pooled = false
jndiName = "java:/comp/env/jdbc/myJndiName"
}
}
}
And everything else is defined by Tomcat - this just needs to match it. There is no need to define any dataSource bean in resources.groovy