My web-app utilizes Geb for functional testing.
It is a non-english application, all page messages being received from i18n message bundle.
How to make Geb work with internationalized messages?
The Grails RemoteControl plugin allows remote access to a running Grails application. In a functional test setting it can be used to read and change configuration settings, access the application context including the message source, … .
The code below is added to a common base class for all our Geb specifications/tests that can be used in an individual test to retrieve an internationalized message:
class BaseTest/Spec {
RemoteControl remoteControl = new RemoteControl()
String msg(String msgKey, args = null, locale = defaultLocale) {
if (args != null) {
args = args as Object[]
}
return remoteControl.exec {
ctx.messageSource.getMessage(msgKey, args, locale)
}
}
}
All my page classes extend from base class: ScaffoldPage
import java.util.ResourceBundle;
import geb.Page
class ScaffoldPage extends Page {
static content = {
resourceBundle {
ResourceBundle bundle = new PropertyResourceBundle(new InputStreamReader(new FileInputStream('./grails-app/i18n/messages_ru.properties'), "UTF-8"))
bundle
}
}
}
Then, at a certain page, I use a construction like this:
class CreatePayeePage extends ScaffoldPage {
static at = {
title == resourceBundle.getString('payee.title.create.label')
}
}
Related
I'm adapting an existing Grails 3 project to a multi-tenant structure, using the schema mode provided by GORM, and I'm having trouble getting the GORM listeners to work when I specify a tenant.
My listener looks like this:
#CompileStatic
class VehicleListenerService {
#Listener(Vehicle)
void onPreInsertEvent(PreInsertEvent event) {
println "*** Vehicle preInsert"
event.entityAccess.setProperty('model', 'preInsert')
}
#Listener(Vehicle)
void onPreUpdateEvent(PreUpdateEvent event) {
println "*** Vehicle preUpdate"
event.entityAccess.setProperty('model', 'preUpdate')
}
}
So every time a vehicle is created or updated, its model should be changed to preInsert or preUpdate.
The current tenant is determined by the subdomain specified in the URL. If I access the app with no subdomain (via http://localhost:8080), the listener works as expected, but if I provide a subdomain (http://test.localhost:8080), the listener doesn't do anything, and the vehicle model doesn't change.
What do I have to do to make the GORM listener work with any tenant?
I've created a sample project (https://github.com/sneira/mtschema) which reproduces the error.
With help from the Grails Slack channel and some more research, I've come up with a solution to this.
First, the listener service has to extend AbstractPersistenceEventListener:
#CompileStatic
class VehicleListenerService extends AbstractPersistenceEventListener {
protected VehicleListenerService(Datastore datastore) {
super(datastore)
}
#Override
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
String newModel =
event.eventType == EventType.PreInsert ? 'preInsert' : 'preUpdate'
event.entityAccess.setProperty('model', newModel)
}
#Override
boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
boolean supportsEvent = eventType.isAssignableFrom(PreInsertEvent) ||
eventType.isAssignableFrom(PreUpdateEvent)
return supportsEvent
}
}
Now we can create a service instance for each schema (except for the default) in Bootstrap.groovy, and add it to our app:
def init = { servletContext ->
def ctx = grailsApplication.mainContext
['TEST', 'TEST2'].each { String name ->
HibernateDatastore ds = hibernateDatastore.getDatastoreForConnection(name)
VehicleListenerService listener = new VehicleListenerService(ds)
ctx.addApplicationListener(listener)
}
}
I've uploaded the complete code to https://github.com/sneira/mtschema/tree/listeners.
How to get values from properties file please? and where should I put the file ?
Thank you
EDIT : I'm using grails 3.1.5 And I'm trying to get properties from a job class (quartz)
Either keep your properties directly in Config.groovy file.
Or you can create a .properties file to keep properties and add this file in Config.groovy
grails.config.locations = [ "classpath:grails-app-config.properties"]
and access it anywhere in application using
grailsApplication.config."propertyName"
We have a trait like this:
/**
* Load config from config locations given by property grails.config.locations.
* Based on http://grails.1312388.n4.nabble.com/Grails-3-External-config-td4658823.html
*/
trait ExternalConfigurationLoader implements EnvironmentAware {
#Override
void setEnvironment(Environment environment) {
loadExternalConfigLocations(environment)
}
void loadExternalConfigLocations(Environment environment) {
if (environment) {
def configLocations = findConfigLocationsFromApplicationGroovy()
DefaultResourceLocator resourceLocator = new DefaultResourceLocator()
for (String configLocation in configLocations) {
loadConfigLocation(configLocation, grails.util.Environment.current.name, environment, resourceLocator)
}
}
}
List<String> findConfigLocationsFromApplicationGroovy() {
def applicationGroovy = this.getClass().classLoader.getResource('application.groovy')
if (applicationGroovy) {
def applicationConfiguration = new ConfigSlurper(grails.util.Environment.current.name).parse(applicationGroovy)
return applicationConfiguration.grails.config.locations
}
[]
}
void loadConfigLocation(String configLocation, String currentEnvironmentName, Environment environment, ResourceLocator resourceLocator) {
def configurationResource = resourceLocator.findResourceForURI(configLocation)
if (configurationResource) {
log.debug "External config '$configLocation' found. Loading."
def configSlurper = new ConfigSlurper(currentEnvironmentName)
def config = configSlurper.parse(configurationResource.getURL())
environment.propertySources.addFirst(new MapPropertySource(configLocation, config))
} else {
log.debug "External config '$configLocation' not found."
}
}
}
Then we can add this trait to Application.groovy:
class Application extends GrailsAutoConfiguration implements ExternalConfigurationLoader {
and configure external config files in application.groovy:
grails.config.locations = ["classpath:myapp-config.groovy", "file:dev-config.groovy"]
If using Tomcat, you can then put myapp-config.groovy in Tomcats lib folder.
Note: this variant only supports external config files of type .groovy but you can extend it to support .yml or .properties if you prefer that. Also note that this example has some issues with overriding values from environment block in application.yml, so if you plan to override dataSource you will need to move the default configuration of dataSource from application.yml to application.groovy first.
There is also a plugin in the making that is adding similar support for grails.config.locations. See https://github.com/sbglasius/external-config
I'm using grails jms-1.3plugin and I have the problem, that my jms listener Service starts consuming messages from activeMQ before the application is fully up and running. This results in an error when I try to write some messages to the DB.
So my question is, how can I manage to start consuming from a queue manually. So that I can set autoStartup to false.
here is my example grails code:
ConsumerService.groovy
package jmsstartstop
import grails.plugin.jms.Queue
class ConsumerService {
static exposes = ["jms"]
#Queue(name="liesMich")
def receiveMessage(String msg) {
log.info("Received Message:" + msg)
}
}
resources.groovy
import org.apache.activemq.ActiveMQConnectionFactory
import org.springframework.jms.connection.SingleConnectionFactory
beans = {
jmsConnectionFactory(SingleConnectionFactory) {
targetConnectionFactory = { ActiveMQConnectionFactory cf ->
brokerURL = grailsApplication.config.jms.brokerURL
}
}
}
Config.groovy
jms{
brokerURL='tcp://localhost:61616'
containers {
standard {
autoStartup = false
}
}
}
What I'm looking for is something like jmsConnectionFactory.getTargetConnectionFactory().start() that can be called in Bootstrap.groovy or maybe in a controller manually. But unfortunately this start method does not exist in the TargetConnectionFactory.
Is there a way to do it, or any other suggestions?
Bootstrap.groovy (which is not working)
class BootStrap {
def jmsConnectionFactory
def init = { servletContext ->
jmsConnectionFactory.??WHATEVER??.start()
}
def destroy = {
}
}
The issue is that the plugin starts processing messages before the Datasource plugin (part of Grails) has finished it's own startup.
The good news is that this appears to be fixed in the latest SNAPSHOT version of the plugin.
To use the SNAPSHOT change your plugin as such: :jms:1.3-SNAPSHOT in your BuildConfig.groovy
What worked for me is to MANUALLY start the JMSListener services on Bootstrap file:
e.g.
In listener.groovy:
class ClientListenerService {
boolean transactional = true
static exposes = ["jms"]
static destination = "com.moviesxd.api.domain.Client_QUEUE"
static isTopic = false
static container = "manualStart"
In bootstrap.groovy:
def clientRequestListenerJmsListenerContainer
...
clientRequestListenerJmsListenerContainer.start()
This solves the problem.
I am lookin for a way to let the admin-role of my grails-app add a "feature"/"plugin"
to the running server, so that the system makes use of it instantly.
To be more concrete here is a small example:
package domains
abstract class Provider {
def protected name;
def protected activated = false;
def private valid;
def Provider( providerName ) {
if( providerName != null ) {
name = providerName;
valid = true;
else valid = false;
}
def isValid() { valid }
def getName() { name }
def isActivated() { activated }
def setActivated( bool ) { activaed = bool }
abstract List<String> search( String searchKey );
}
Some Subclass:
package googleprovider
import Provider;
class GoogleProvider extends Provider {
def GooleProvider( active ) {
super( "Google" );
activated = active;
}
#Override
List<String> search( String searchKey ) {
return ["http://www.google.com"]
}
}
Now every "plugin"/"feature" should extend from Provider and be placed as what ever file in a directory "plugins/providers/".
And the server should create an instance of this GoogleProvider on an "onAdd"-event or something leashed by that admin.
Is there any chance this could be done? Or am I totally dreaming?
If it is somehow possible and it's just that I am going a completly wrong direction,
just tell me! ;-)
Thanks for your time!
I suggest you look for plugins that registers new Artefacts, so in your startup you can lookup for this classes. You can also create a folder in grails-app to store the providers classes.
See the Grails Dao Artefacts plugin, for example. It creates the daos folder inside grails-app, and consider all classes as a DAO Artefact. You also gain the ability of use Depenceny Injection in your classes (e.g. services).
Some points to look
Install Script creates the directory
You have some classes that declare the Artefact
The plugin descriptor is responsible to register them as Spring Beans
More info in
Grails developer wiki
Blog post with example
It appears the convention for converting objects in Groovy is to use the as operator and override asType(). For example:
class Id {
def value
#Override
public Object asType(Class type) {
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
def formattedId = new Id(value: "test") as FormattedId
However, Grails over-writes the implementation of asType() for all objects at runtime so that it can support idioms like render as JSON.
An alternative is to re-write the asType() in the Grails Bootstrap class as follows:
def init = { servletContext ->
Id.metaClass.asType = { Class type ->
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
However, this leads to code duplication (DRY) as you now need to repeat the above in both the Bootstrap and the Id class otherwise the as FormattedId will not work outside the Grails container.
What alternatives exist to writing conversion code in Groovy/Grails that do not break good code/OO design principals like the Single Responsibility Principal or DRY? Are Mixins are good use here?
You can use the Grails support for Codecs to automatically add encodeAs* functions to your Grails archetypes:
class FormattedIdCodec {
static encode = { target ->
new FormattedId((target as String).toUpperCase()
}
}
Then you can use the following in your code:
def formattedId = new Id(value: "test").encodeAsFormattedId
My un-elegant solution is to rename the original asType(), and make a new asType() that calls it, and to also make your BootStrap overwrite astType with a call to that method:
so, your class:
class Id {
def value
#Override
public Object asType(Class type) {
return oldAsType(type);
}
public Object oldAsType(Class type) {
if (type == FormattedId) {
return new FormattedId(value: value.toUpperCase())
}
}
}
In my app, I had asType defined in a number of classes, so I ended up using a common closure in BootStrap.groovy:
def useOldAsType = {Class clazz ->
delegate.oldAsType(clazz)
}
Id.metaClass.asType = useOldAsType;
Value.metaClass.asType = useOldAsType;
OtherClass.metaClass.asType = useOldAsType;
SubclassOfValue.metaClass.asType = useOldAsType;
Note that if you have a subclass that does not override asType, but you want it to use the superclass's, you must also set it in BootStrap.