After many years with no posting I dare creating my first entry in English. A few months ago I was playing around Grails 3 migration from a Grails 2.3.11 project and these were my steps to finish the process:
- Create a new project via Intellij with Grails 3.2.9 and Java 8
grails create-app education
|
When executing create-app command, by default the "web" profile is used. |
- Place all files from Grails 2 project to Grails 3 project respecting packages
Old Location | New Location | Description |
grails-app/conf/BuildConfig.groovy | build.gradle | Build time configuration is now defined in a Gradle build file |
grails-app/conf/Config.groovy | grails-app/conf/application.groovy | Renamed for consistency with Spring Boot |
grails-app/conf/UrlMappings.groovy | grails-app/controllers/UrlMappings.groovy | Moved since grails-app/conf is not a source directory anymore |
grails-app/conf/BootStrap.groovy | grails-app/init/BootStrap.groovy | Moved since grails-app/conf is not a source directory anymore |
scripts | src/main/scripts | Moved for consistency with Gradle |
src/groovy | src/main/groovy | Moved for consistency with Gradle |
src/java | src/main/groovy (yes, groovy!) | Moved for consistency with Gradle |
test/unit | src/test/groovy | Moved for consistency with Gradle |
test/integration | src/integration-test/groovy | Moved for consistency with Gradle |
web-app |
src/main/webapp or src/main/resources/
| Moved for consistency with Gradle |
\*GrailsPlugin.groovy | src/main/groovy | The plugin descriptor moved to a source directory |
src/main/resources/public is recommended as src/main/webapp only gets included in WAR packaging but not in JAR packaging.
It is recommended to merge Java source files from src/java into src/main/groovy . You can create a src/main/java
directory if you want to and it will be used but it is generally better
to combine the folders. (The Groovy and Java sources compile together.)
|
- Our BuildConfig.groovy in Grails 2 project will be our build.gradle in Grails 3 project
buildscript {
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
classpath "org.grails:grails-gradle-plugin:$grailsVersion"
classpath "com.bertramlabs.plugins:asset-pipeline-gradle:2.14.1"
classpath "org.grails.plugins:hibernate5:${gormVersion-".RELEASE"}"
classpath "org.grails.plugins:database-migration:3.0.0"
}
}
version "1.1"
group "education"
apply plugin:"eclipse"
apply plugin:"idea"
apply plugin:"war"
apply plugin:"org.grails.grails-web"
apply plugin:"org.grails.grails-gsp"
apply plugin:"asset-pipeline"
repositories {
mavenLocal()
maven { url "https://repo.grails.org/grails/core" }
}
dependencies {
compile "org.springframework.boot:spring-boot-starter-logging"
compile "org.springframework.boot:spring-boot-autoconfigure"
compile "org.grails:grails-core"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "org.springframework.boot:spring-boot-starter-tomcat"
compile "org.grails:grails-dependencies"
compile "org.grails:grails-web-boot"
compile "org.grails.plugins:cache"
compile "org.grails.plugins:scaffolding"
compile "org.grails.plugins:hibernate5"
compile "org.hibernate:hibernate-core:5.1.3.Final"
compile "org.hibernate:hibernate-ehcache:5.1.3.Final"
console "org.grails:grails-console"
profile "org.grails.profiles:web"
runtime "com.bertramlabs.plugins:asset-pipeline-grails:2.14.1"
runtime "com.h2database:h2"
testCompile "org.grails:grails-plugin-testing"
testCompile "org.grails.plugins:geb"
testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
// MySQL 5 Driver
runtime "mysql:mysql-connector-java:5.1.36"
// Ajax Tags
compile "org.grails.plugins:ajax-tags:1.0.1.BUILD-SNAPSHOT"
// Joda Time
compile "joda-time:joda-time:2.9.6"
// Rest Client Builder
compile "org.grails:grails-datastore-rest-client"
// Database Migration
compile "org.grails.plugins:database-migration:3.0.0"
compile "org.liquibase:liquibase-core:3.5.3"
// Spring Security Core
compile "org.grails.plugins:spring-security-core:3.1.1"
// Spring Security ACL
compile "org.grails.plugins:spring-security-acl:3.1.0"
// Spring Security Rest
//compile "org.grails.plugins:spring-security-rest:2.0.0.M2"
// Email
compile "org.grails.plugins:mail:2.0.0.RC6"
// Export
compile "org.grails.plugins:export:2.0.0"
// Google Visualization
compile "org.grails.plugins:grails-google-visualization:2.2"
// CKEditor
compile "org.grails.plugins:ckeditor:4.5.9.0"
}
bootRun {
jvmArgs('-Dspring.output.ansi.enabled=always')
addResources = true
}
sourceSets {
main {
resources {
srcDir 'grails-app/migrations'
}
}
}
assets {
minifyJs = true
minifyCss = true
}
|
There will be plugins already created for Grails 3 distribution but others don't. For these ones we must look for a workaround:
- Convert them by ourselves to Grails 3
- Get rid of them and implement them by code
|
- Our Datasource.groovy in Grails 2 project will be in our application.yml in Grails 3 project
---
grails:
profile: web
codegen:
defaultPackage: net.rdcstudios.core
spring:
transactionManagement:
proxies: false
gorm:
# Whether to autowire entities.
# Disabled by default for performance reasons.
autowire: true # Changed to true to be able to inject services into domain classes
reactor:
# Whether to translate GORM events into Reactor events
# Disabled by default for performance reasons
events: false
info:
app:
name: '@info.app.name@'
version: '@info.app.version@'
grailsVersion: '@info.app.grailsVersion@'
spring:
main:
banner-mode: "off"
groovy:
template:
check-template-location: false
# Spring Actuator Endpoints are Disabled by Default
endpoints:
enabled: false
jmx:
enabled: true
---
grails:
mime:
disable:
accept:
header:
userAgents:
- Gecko
- WebKit
- Presto
- Trident
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
pdf: application/pdf
rss: application/rss+xml
text: text/plain
hal:
- application/hal+json
- application/hal+xml
xml:
- text/xml
- application/xml
urlmapping:
cache:
maxsize: 1000
controllers:
defaultScope: singleton
converters:
encoding: UTF-8
views:
default:
codec: html
gsp:
encoding: UTF-8
htmlcodec: xml
codecs:
expression: html
scriptlets: html
taglib: none
staticparts: none
endpoints:
jmx:
unique-names: true
---
hibernate:
cache:
queries: false
use_second_level_cache: true
use_query_cache: false
region.factory_class: org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
dataSource:
pooled: true
jmxExport: true
driverClassName: com.mysql.jdbc.Driver
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
# dialect: net.rdcstudios.mysql.dialect.MySQLUTF8MB4InnoDBDialect
username: core
password: core
environments:
development:
dataSource:
dbCreate: none
internaldemo:
dataSource:
dbCreate: none
demo:
dataSource:
dbCreate: none
client:
dataSource:
dbCreate: none
test:
dataSource:
dbCreate: create-drop
uat:
dataSource:
dbCreate: none
production:
dataSource:
dbCreate: none
properties:
jmxEnabled: true
initialSize: 5
maxActive: 50
minIdle: 5
maxIdle: 25
maxWait: 10000
maxAge: 600000
timeBetweenEvictionRunsMillis: 5000
minEvictableIdleTimeMillis: 60000
validationQuery: SELECT 1
validationQueryTimeout: 3
validationInterval: 15000
testOnBorrow: true
testWhileIdle: true
testOnReturn: false
jdbcInterceptors: ConnectionState
defaultTransactionIsolation: 2 # TRANSACTION_READ_COMMITTED
|
- Our log4j which is inside Config.groovy in Grails 2 project will be our logback.groovy in Grails 3 project
import grails.util.BuildSettings
import grails.util.Environment
import org.springframework.boot.logging.logback.ColorConverter
import org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter
conversionRule 'clr', ColorConverter
conversionRule 'wex', WhitespaceThrowableProxyConverter
// See http://logback.qos.ch/manual/groovy.html for details on configuration
//statusListener(OnConsoleStatusListener)
def appenderList = ["ROLLING"]
def LOG_DIR = "."
def consoleAppender = true
def targetDir = BuildSettings.TARGET_DIR
if (targetDir != null) {
appenderList.add("CONSOLE")
LOG_DIR = "/var/log/education/${Environment.currentEnvironment.name}"
} else {
LOG_DIR = "/var/log/education/${Environment.currentEnvironment.name}"
consoleAppender = false
}
if (consoleAppender) {
appender("CONSOLE", ConsoleAppender) {
encoder(PatternLayoutEncoder) {
pattern = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
}
}
}
appender("ROLLING", RollingFileAppender) {
encoder(PatternLayoutEncoder) {
Pattern = "%d %level %thread %mdc %logger - %m%n"
}
rollingPolicy(SizeAndTimeBasedRollingPolicy) {
MaxFileSize = "5MB"
FileNamePattern = "${LOG_DIR}/log.%d{yyyy-MM-dd}.%i"
}
}
logger 'grails.app.services', INFO, appenderList
logger 'grails.app.controllers', INFO, appenderList
logger 'net.rdcstudios.core', INFO, appenderList
//root(INFO, appenderList)
// Spring Security
//logger 'grails.plugin.springsecurity.web.filter.DebugFilter', INFO, ['STDOUT']
// Hibernate Logs
//logger 'org.hibernate.SQL', DEBUG, ['STDOUT']
//logger 'org.hibernate.type.descriptor.sql.BasicBinder', TRACE, ['STDOUT']
|
org.codehaus.groovy.grails.commons.GrailsApplication --> grails.core.GrailsApplication
org.codehaus.groovy.grails.core.io.ResourceLocator --> org.grails.core.io.ResourceLocator
org.codehaus.groovy.grails.web.mapping.LinkGenerator --> grails.web.mapping.LinkGenerator
org.codehaus.groovy.grails.web.binding.DataBindingUtils --> grails.web.databinding.DataBindingUtils
org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap --> grails.web.servlet.mvc.GrailsParameterMap
In unit or integration tests...
org.codehaus.groovy.grails.plugins.testing.GrailsMockMultipartFile --> org.grails.plugins.testing.GrailsMockMultipartFile
|
- As
of now Grails 3 uses Pipeline Plugin to work with assets instead of
Resource Plugin in Grails 2. Therefore, we have to move all assets:
- web-app/css --> grails-app/assets/stylesheets
- web-app/images --> grails-app/assets/images
- web-app/js --> grails-app/assets/javascripts
- Create application.js as an index of all js files
// This is a manifest file that'll be compiled into application.js.
//
// Any JavaScript file within this directory can be referenced here using a relative path.
//
// You're free to add application-wide JavaScript to this file, but it's generally better
// to create separate JavaScript files as needed.
//
//= require jquery-2.2.0.min
//= require ie10-viewport-bug-workaround
//= require rs-plugin/js/jquery.themepunch.tools.min
//= require rs-plugin/js/jquery.themepunch.revolution.min
//= require custom
//= require menu/jquery.mmenu.min.all
//= require bootstrap/bootstrap.min
//= require bootstrap/moment
//= require bootstrap/bootstrap-datetimepicker
//= require bootstrap/bootstrap-switch.min
//= require bootstrap/bootstrap-wizard
//= require bootstrap/bootstrap-combobox
//= require fullcalendar/fullcalendar.min
//= require fullcalendar/lang-all
//= require_self
if (typeof jQuery !== 'undefined') {
(function($) {
$(document).ajaxStart(function() {
$('#spinner').fadeIn();
}).ajaxStop(function() {
$('#spinner').fadeOut();
});
})(jQuery);
}
|
- Create application.css as an index of all css files
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS file within this directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= encoding UTF-8
*= require bootstrap/bootstrap-responsive
*= require pure-drawer
*= require style
*= require font-awesome/css/font-awesome.min
*= require menu/jquery.mmenu.all
*= require rs-plugin/css/extralayers
*= require rs-plugin/css/settings
*= require bootstrap/bootstrap-datetimepicker
*= require bootstrap/bootstrap-switch.min
*= require bootstrap/bootstrap-wizard
*= require bootstrap/bootstrap-combobox
*= require fullcalendar/fullcalendar.min
*= require_self
*/
|
- Add application.js and application.css to main.gsp
<asset:stylesheet src="application.css"/>
<asset:javascript src="application.js"/>
|
- Change all <img> and <g:img> to <asset:image src="" />
- Change all <script>, <g:script> and <r:script> to <asset:script type="text/javascript">
- I had to add this line just before
</body>
in the outer main template to make javascript code embedded in views or templates work
- Change all ${resource()} methods to ${assetPath(src: ''}
- Change all <link> related to css to <asset:link>
- Change all path in templates from ../ to /
<g:render template="../layouts/bottomBar"/>
to
<g:render template="/layouts/bottomBar"/>
|
- Remove all grailsApplication beans from Controllers as Grails 3 imports them automatically
- Install
new plugin called ajax-tags as Grails 3 removes all taglibs related to
remote and to keep using them we will need to have it installed
// Ajax Tags plugin
compile "org.grails.plugins:ajax-tags:1.0.1.BUILD-SNAPSHOT"
|
- Change all prefixes about respond method convention
Example | Argument Type | Calculated Model Variable |
respond Book.list() | java.util.List | bookList |
respond Book.get(1) | example.Book | book |
respond( [1,2] ) | java.util.List | integerList |
respond( [1,2] as Set ) | java.util.Set | integerSet |
respond( [1,2] as Integer[] ) | Integer[] | integerArray |
// course/_grid
<g:each in="${courseInstanceList}" status="i" var="courseInstance">
to
<g:each in="${courseList}" status="i" var="courseInstance">
|
- Change all references to config
grailsApplication.config.net.rdcstudios.pagination.size
to
grailsApplication.config.getProperty('net.rdcstudios.pagination.size')
|
- Change all references to grailsResourceLocator
grailsResourceLocator.findResourceForURI()
to
assetResourceLocator.findAssetForURI()
|
- Change all references to domainClass
instance.domainClass.propertyName
to
grails.util.GrailsNameUtils.getPropertyName(instance.class)
|
- To make external config properties work, we will need to modify the class Application.groovy
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.springframework.context.EnvironmentAware
import org.springframework.core.env.Environment
import org.springframework.core.env.MapPropertySource
class Application extends GrailsAutoConfiguration implements EnvironmentAware {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
@Override
void setEnvironment(Environment environment) {
def file = new File("/etc/core/core-${grails.util.Environment.getCurrent().name}-config.properties")
if(file.exists()) {
def config = new ConfigSlurper().parse(file.text)
environment.propertySources.addFirst(new MapPropertySource(grails.util.Environment.getCurrent().name, config))
}
}
}
|
- Plus
to escape all values from external config properties we will need to
add quotes "..." as YAML Parser launches :, . and / values as yml values
dataSource.url = jdbc:mysql://localhost/rdcstudios_dev
to
dataSource.url = "jdbc:mysql://localhost/rdcstudios_dev"
|
- Move all global constraints from Config.groovy to application.groovy
// Shared constraints
grails.gorm.default.constraints = {
codeConstraint(blank: false, nullable: true, size: 1..4, unique: 'country')
}
|
- Don't forget to change empty constraints from () to [:]
static constraints = {
name nullable: true, blank: false
mySimpleProperty() // <- A field that has no constraints. This syntax is not supported in Grails 3.
anotherProperty unique: true
}
to
static constraints = {
name nullable: true, blank: false
mySimpleProperty [:] // <- Empty map argument instead of ()
anotherProperty unique: true
}
|
- All
services injected into domain classes don't work. Why? In Grails 2
projects they were working property. The reason is that in Grails 3
there is a property called grails.gorm.autowired in application.yml set to false by default. This property is disabled by default for performance reasons.
- Every time app throws an exception is displayed the following:
Caused by: groovy.lang.MissingMethodException: No signature of method: ch.qos.logback.classic.Logger.error() is applicable for argument types: (org.springframework.dao.DataIntegrityViolationException)
|
Why?
This is because the default logger that is added to Grails artefacts is
now an instance of SLF4J, and not JCL. Unlike JCL, none of the SLF4J
logging methods take a single
Object
argument. Therefore we need to do some changes:
catch (DataIntegrityViolationException dive) {
log.error(dive)
flash.error = message(code: 'default.not.deleted.message', args: ["User", id], default: "User could not be deleted")
redirect(action: 'show', id: userInstance?.id)
}
to
catch (DataIntegrityViolationException dive) {
log.error(dive.toString())
flash.error = message(code: 'default.not.deleted.message', args: ["User", id], default: "User could not be deleted")
redirect(action: 'show', id: userInstance?.id)
}
|
UNIT TESTS
No changes
INTEGRATION TESTS
- I changed extends IntegrationSpec to Specification from all tests to keep rules as per Grails 3 doc.
- I added @Integration and @Rollback annotation at class level.
@Integration
@Rollback
class ActivityServiceIntegrationSpec extends Specification {
...
}
|
- I had to change some load methods to get method as in Grails 3 data binding is more stricted.
def setupData() {
def user = User.load(1)
def course = new Course(shortName: 'Test shortname', summary: 'Test summary', owner: user).save() -> LazyInitializationException
}
to
def setupData() {
def user = User.get(1)
def course = new Course(shortName: 'Test shortname', summary: 'Test summary', owner: user).save() -> Works fine
}
|
- As per Grails 3 it's not allowed to save any instance in setup method when @Rollback annotation is not added as app will throw HibernateException: No Session found. To solve this it's recommended to added @Rollback annotation and separate setup method (for non transactional operations) from setupData method (for transactional operations). Don't forget calling setupData method from given block
def setup() {
alfrescoService.metaClass.uploadFile = { Object resource, MultipartFile resourceFile -> "1" }
alfrescoService.metaClass.deleteFileByNodeRef = { String nodeRef -> true }
}
def setupData() {
def user = User.get(1)
def course = new Course(shortName: 'Test shortname', summary: 'Test summary', owner: user).save()
}
...
void "test save method"() {
given:
setupData()
...
}
|
You have two possibilities:
- Adding
@Rollback you will have Hibernate session in setup method but once
finished test there will be no rollback. If you split it in setup method
for non transactional operations and setupData method for transactional
operations, you will get rollback.
- Without adding @Rollback you will get no Hibernate session in setup method.
|
DEPLOYMENT ON JENKINS
- Below I'll paste changes I had to do in order to migrate deployment to Grails 3 on Jenkins job
| Grails 2 job | Grails 3 job |
GENERATE WAR FILE | grails war education.war | grails war |
RUN TESTS | grails test-app -coverage -xml | grails test-app |
GENERATE DOC | grails doc | grails docs |
|
|
|
PUBLISH COVERAGE REPORTS | target/test-reports/cobertura/coverage.xml | build/reports/cobertura/coverage.xml |
DEPLOY TO TOMCAT | (no changes) | (no changes) |
EMAIL | (no changes) | (no changes) |
- On Grails 2 war file was generated under project root path but on Grails 3 war file is generated in build / libs path so I had to add some lines to change path in build.gradle
// build.gradle file
war {
archiveName "${baseName}.war" // This line for not adding version to war file name
destinationDir project.projectDir // This line for setting path to project root path and not in build / libs
}
|
- When deploying to Tomcat Jenkins threw this weird error
java.lang.NoSuchMethodError: org.apache.tomcat.util.res.StringManager.getManager(Ljava/lang/Class;)Lorg/apache/tomcat/util/res/StringManager;
|
After
googling a little bit, I figured out it was due to use different Tomcat
versions among embed Tomcat used by Spring Boot (version 8) and Tomcat
used by Jenkins (version 7). As a workaround I changed this
// build.gradle file
compile "org.springframework.boot:spring-boot-starter-tomcat"
to
provided "org.springframework.boot:spring-boot-starter-tomcat"
|
- To keep using coverage plugin as of now you can use Gradle plugin. For it the only thing you need to do is to add some lines to build.gradle file
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "net.saliman:gradle-cobertura-plugin:2.4.0"
}
}
apply plugin: "net.saliman.cobertura"
|
- Also you will need to add cobertura after running tests
// Cobertura configuration
cobertura {
coverageFormats = ['xml']
coverageExcludes = ['.*Application.*',
'.*BootStrap.*',
'.*UrlMappings.*']
}
test.finalizedBy(project.tasks.cobertura)
|
INTELLIJ IDEA 2017
- Execute all tests under JUnit not under Grails environment, otherwise Intellij IDEA will execute all tests.
- Sometimes
Intellij IDEA executes the test under production or development env
instead of test env. To amend it you just have to add
-Dgrails.env=production to command line from Run / Debug configuration
// Test log trace
The following profiles are active: production
|
- Sometimes test throws an error related to Cannot add Domain Class [class ...] It is not a Domain!. To fix it,
SPRING SECURITY CORE
- Our
Config.groovy in Grails 2 project contained all configuration
properties related to plugin. As of now we will have to create a new
file called application.groovy to put them in
// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.debug.useFilter = true
grails.plugin.springsecurity.securityConfigType = "Annotation"
grails.plugin.springsecurity.logout.postOnly = false
grails.plugin.springsecurity.userLookup.userDomainClassName = "net.rdcstudios.core.security.User"
grails.plugin.springsecurity.userLookup.authorityJoinClassName = "net.rdcstudios.core.security.UserRole"
grails.plugin.springsecurity.authority.className = "net.rdcstudios.core.security.Role"
grails.plugin.springsecurity.adh.errorPage = null
grails.plugin.springsecurity.rejectIfNoRule = false
grails.plugin.springsecurity.fii.rejectPublicInvocations = false
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/', access: ['permitAll']],
[pattern: '/index', access: ['permitAll']],
[pattern: '/index.gsp', access: ['permitAll']],
[pattern: '/assets/**', access: ['permitAll']]
]
grails.plugin.springsecurity.useSecurityEventListener = true
|
- Plus as we need to load an event related to plugin at runtime, we will create other file called runtime.groovy to define it
grails.plugin.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
// Setting language by country and language fields from user domain class
net.rdcstudios.core.security.User.withSession {
net.rdcstudios.core.security.User user = net.rdcstudios.core.security.User.get(e.source.principal.id)
def languageCode = user?.language?.code
def countryCode = user?.country?.code
def request = grails.plugin.springsecurity.web.SecurityRequestHolder.getRequest()
if (languageCode && countryCode && request) {
def locale = new Locale(languageCode, countryCode)
if (locale) {
org.grails.web.util.WebUtils.setSessionAttribute(request, org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, locale)
}
}
}
}
|
DATABASE MIGRATION
- We could not install the new plugin for Grails 3 as we were working with Java 7 and app threw this error,
Unsupported major.minor version 52.0
INFO
J2SE 8 = 52
J2SE 7 = 51
J2SE 6.0 = 50
J2SE 5.0 = 49
JDK 1.4 = 48
JDK 1.3 = 47
JDK 1.2 = 46
JDK 1.1 = 45
|
When upgrading to Java 8 error disappeared.
EXPORT
- I found an error to display export buttons. In Grails 2 projects you set it by adding Resource plugin tag
<r:require module="export"/>
|
But
in Grails 3 projects we use Asset Pipeline so no way to reference
resources belonging to plugins. I realized manifest file from Export
plugin was called application.css (the same name as my app one). So as a
workaround I had to rename my app manifest to app.css and leave
application.css for Export plugin one.
<asset:stylesheet src="application.css"/>
|
I had to add this line just on views where it was used.
- Plus I had to add the following line to build.gradle
assets {
minifyJs = true
minifyCss = true
packagePlugin = true
}
|
ASSET PIPELINE DOCUMENTATION
Plugins
also can have the same "grails-app/assets" folder and their URL mapping
is also the same. This means it can be more important to ensure unique
naming / path mapping between plugins. This is also powerful in the
sense that a plugin can add helper manifests to be used within your apps
like jquery, bootstrap, font-awesome, and more.
Plugins should make sure that the assets { packagePlugin } property is set to true in their build.gradle file otherwise assets will not properly be packaged into the plugin for use by the application.
|
JASPER
- I found no plugin compatible for Grails 3 so I had to do the following:
- Import JasperController to my controllers folder
- Import JasperService to my services folder
- Import JasperTagLib to my taglib folder (changing all g.resource references to asset.assetPath)
- Import all Jasper images to my assets/images folder
- Import JasperExportFormat and JasperReportDef to src/main/groovy
- Plus I had to add the following lines to build.gradle
// Jasper
compile "com.lowagie:itext:2.1.7"
compile("net.sf.jasperreports:jasperreports:5.6.1") {
exclude group: 'antlr', module: 'antlr'
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'org.apache.ant', module: 'ant'
exclude group: 'mondrian', module: 'mondrian'
exclude group: 'commons-javaflow', module: 'commons-javaflow'
exclude group: 'net.sourceforge.barbecue', module: 'barbecue'
exclude group: 'xml-apis', module: 'xml-apis'
exclude group: 'xalan', module: 'xalan'
exclude group: 'org.codehaus.groovy', module: 'groovy-all'
exclude group: 'org.hibernate ', module: 'hibernate'
exclude group: 'javax.xml.soap', module: 'saaj-api'
exclude group: 'javax.servlet', module: 'servlet-api'
exclude group: 'org.springframework', module: 'spring-core'
exclude group: 'org.beanshell', module: 'bsh'
exclude group: 'org.springframework', module: 'spring-beans'
exclude group: 'jaxen', module: 'jaxen'
exclude group: 'net.sf.barcode4j ', module: 'barcode4j'
exclude group: 'org.apache.xmlgraphics', module: 'batik-svg-dom'
exclude group: 'org.apache.xmlgraphics', module: 'batik-xml'
exclude group: 'org.apache.xmlgraphics', module: 'batik-awt-util'
exclude group: 'org.apache.xmlgraphics', module: 'batik-dom'
exclude group: 'org.apache.xmlgraphics', module: 'batik-css'
exclude group: 'org.apache.xmlgraphics', module: 'batik-gvt'
exclude group: 'org.apache.xmlgraphics', module: 'batik-script'
exclude group: 'org.apache.xmlgraphics', module: 'batik-svggen'
exclude group: 'org.apache.xmlgraphics', module: 'batik-util'
exclude group: 'org.apache.xmlgraphics', module: 'batik-bridge'
exclude group: 'javax.persistence', module: 'persistence-api'
exclude group: 'eclipse', module: 'jdtcore'
exclude group: 'org.olap4j', module: 'olap4j'
}
compile "org.apache.poi:poi:3.10-FINAL"
compile "commons-io:commons-io:2.5"
|
BULK DATA IMPORTS
- I found no plugin compatible for Grails 3 so I had to do the following:
- Import ImportsController to my controllers folder
- Import ImportsService to my services folder
- Import DefaultImporter and ImportsException to src/main/groovy. Plus logging folder (with all Logguers) to src/main/groovy too.
- Plus
I had to copy all code from doWithSpring closure of
BulkDataImportsGrailsPlugin in Grails 2 to doWithSpring closure of
Application in Grails 3
class Application extends GrailsAutoConfiguration implements EnvironmentAware {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
@Override
void setEnvironment(Environment environment) {
def file = new File("/etc/core/core-${grails.util.Environment.getCurrent().name}-config.properties")
if(file.exists()) {
def config = new ConfigSlurper().parse(file.text)
environment.propertySources.addFirst(new MapPropertySource(grails.util.Environment.getCurrent().name, config))
}
}
@Override
Closure doWithSpring() {
def loggingProvider = Holders.config.grails.plugins.imports.containsKey('loggingProvider') ? Holders.config.grails.plugins.imports.loggingProvider : 'default'
def loggingProviderClass = null
if (loggingProvider == 'mongo') {
loggingProviderClass = MongoLogger
} else if (loggingProvider == 'mem') {
loggingProviderClass = InMemoryLogger
} else if (loggingProvider == 'default') {
loggingProviderClass = DefaultLogger
} else {
Class clazz = Class.forName(loggingProvider, true, Thread.currentThread().contextClassLoader)
loggingProviderClass = clazz
}
def beans = {
importsLogger(loggingProviderClass)
}
for(service in Holders.grailsApplication.serviceClasses) {
if (service.hasProperty('imports')) {
def entityName,
imports = service.getPropertyValue('imports')
if (imports instanceof Class || imports instanceof String) {
entityName = GrailsNameUtils.getPropertyName(imports)
}
if (entityName) {
def found = Holders.grailsApplication.domainClasses?.find { GrailsNameUtils.getPropertyName(it.name) == entityName} != null
if (!found && !service.hasMetaMethod('processRow', getArgs(5)) ) {
log.warn('\n BulkDataImports: could not configure importer '+service.shortName+'... no domain class found and missing processRow method')
} else {
DefaultImporter.SERVICE_METHODS.each { k, v->
if (!service.hasMetaMethod(k, getArgs(v))) service.metaClass."${k}" = DefaultImporter."${k}"
}
def props = DefaultImporter.SERVICE_PROPERTIES.clone()
props.entityName = entityName
props.each {k, v->
if (!service.hasMetaMethod(k)) service.metaClass."${k}" = { -> v }
}
ImporterService.IMPORT_CONFIGURATIONS[entityName] = GrailsNameUtils.getPropertyNameRepresentation(service.shortName)
}
} else {
log.warn('\n BulkDataImports: invalid imports configuration for '+service.shortName+' :'+imports)
}
}
}
ImporterService.IMPORT_CONFIGURATIONS.each { k, v-> log.info('\n BulkDataImports:'+ k + ' imported by '+v) }
return beans
}
private getArgs(ct) {
(1..ct).collect { Object.class }.toArray()
}
}
|
Hope this helps.
Thank you so much for sharing detailed process of migrating grails 2.x to grails 3.x!
ResponderEliminarAs a side note , your English is very good !
thanks now I use Jasper in Grails 3 because does not exist the definitive guide to use Birt with Grails 3.
ResponderEliminarAlvaro León Silvano
Wow, this is amazing! You have done a great service here. I am close to completing my Grails 2x->3.3.8 upgrade and this has been very helpful. And you got Jasper to work?? Amazing.
ResponderEliminarI hope I can contribute some more at some point as I am scripting a lot of the file copy/edit operations. It's very time consuming to make this conversion while the source system is under constant revision.
Thanks again