diff --git a/gradle/test-webjar-asset-config.gradle b/gradle/test-webjar-asset-config.gradle index 4c20a0a7956..28d45306771 100644 --- a/gradle/test-webjar-asset-config.gradle +++ b/gradle/test-webjar-asset-config.gradle @@ -21,10 +21,13 @@ assets { excludes = [ 'webjars/jquery/**', 'webjars/bootstrap/**', + 'webjars/bootstrap-icons/**' ] includes = [ 'webjars/jquery/*/dist/jquery.js', 'webjars/bootstrap/*/dist/js/bootstrap.bundle.js', 'webjars/bootstrap/*/dist/css/bootstrap.css', + 'webjars/bootstrap-icons/*/font/bootstrap-icons.css', + 'webjars/bootstrap-icons/*/font/fonts/*', ] } \ No newline at end of file diff --git a/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/DomainServiceLocator.java b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/DomainServiceLocator.java new file mode 100644 index 00000000000..cef45755687 --- /dev/null +++ b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/DomainServiceLocator.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package grails.plugin.scaffolding; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.springframework.context.ApplicationContext; + +import grails.util.Environment; +import grails.util.Holders; +import org.grails.datastore.gorm.GormEntity; + +/** + * Resolves the appropriate service bean for a given domain class by: + * - Scanning only beans of type GormService + * - Matching via: ((GormEntity) service.getResource()).instanceOf(domainClass) + * + * Keeps a single shared cache for the whole app. + */ +public final class DomainServiceLocator { + + private static final ConcurrentMap, GormService> CACHE = new ConcurrentHashMap<>(); + + private DomainServiceLocator() {} + + /** Resolve (and cache) a service bean for the given domain class. */ + public static > GormService resolve(Class domainClass) { + if (!Environment.isDevelopmentMode()) { + @SuppressWarnings("unchecked") + GormService cached = (GormService) CACHE.get(domainClass); + if (cached != null) return cached; + } + + GormService found = findService(domainClass); + if (!Environment.isDevelopmentMode()) { + CACHE.put(domainClass, found); + } + return found; + } + + /** Clear cache (useful in tests/dev reloads). */ + public static void clear() { + CACHE.clear(); + } + + private static > GormService findService(Class domainClass) { + ApplicationContext ctx = Holders.getGrailsApplication().getMainContext(); + + String[] names = ctx.getBeanNamesForType(GormService.class); + GormService match = null; + List matchingBeanNames = new ArrayList<>(); + + for (String name : names) { + GormService gs = (GormService) ctx.getBean(name); + Object resource = gs.getResource(); + if (resource instanceof GormEntity) { + GormEntity ge = (GormEntity) resource; + if (ge.instanceOf(domainClass)) { + matchingBeanNames.add(name); + if (match != null) { + throw new IllegalStateException( + "Multiple GormService beans match domain " + domainClass.getName() + + ": " + matchingBeanNames + ); + } + @SuppressWarnings("unchecked") + GormService svc = (GormService) gs; + match = svc; + } + } + } + + if (match == null) { + throw new IllegalStateException( + "No GormService bean found for domain " + domainClass.getName() + + " using resource.instanceOf(..). Scanned " + names.length + " GormService beans." + ); + } + + return match; + } +} + diff --git a/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/RestfulServiceController.groovy b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/RestfulServiceController.groovy index 572823356ad..413fc442eab 100644 --- a/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/RestfulServiceController.groovy +++ b/grails-scaffolding/src/main/groovy/grails/plugin/scaffolding/RestfulServiceController.groovy @@ -19,45 +19,52 @@ package grails.plugin.scaffolding +import groovy.transform.CompileStatic import grails.artefact.Artefact import grails.gorm.transactions.ReadOnly import grails.rest.RestfulController -import grails.util.Holders -import org.grails.datastore.gorm.GormEntityApi +import org.grails.datastore.gorm.GormEntity @Artefact('Controller') @ReadOnly -class RestfulServiceController extends RestfulController { +@CompileStatic +class RestfulServiceController> extends RestfulController { RestfulServiceController(Class resource, boolean readOnly) { super(resource, readOnly) } - protected def getService() { - Holders.grailsApplication.getMainContext().getBean(resourceName + 'Service') + protected GormService getService() { + DomainServiceLocator.resolve(resource) } + @Override protected T queryForResource(Serializable id) { getService().get(id) } + @Override protected List listAllResources(Map params) { getService().list(params) } + @Override protected Integer countResources() { getService().count() } + @Override protected T saveResource(T resource) { getService().save(resource) } + @Override protected T updateResource(T resource) { getService().save(resource) } + @Override protected void deleteResource(T resource) { - getService().delete(((GormEntityApi) resource).ident()) + getService().delete(resource.ident()) } } diff --git a/grails-test-examples/scaffolding/build.gradle b/grails-test-examples/scaffolding/build.gradle new file mode 100644 index 00000000000..a45c4960b19 --- /dev/null +++ b/grails-test-examples/scaffolding/build.gradle @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + id 'groovy' + id 'org.apache.grails.gradle.grails-gsp' + id 'org.apache.grails.gradle.grails-web' + id 'cloud.wondrify.asset-pipeline' +} + +version = "0.0.1" +group = "com.example" + +dependencies { + console "org.apache.grails:grails-console" + implementation platform(project(':grails-bom')) + implementation "org.springframework.boot:spring-boot-starter-logging" + implementation "org.springframework.boot:spring-boot-starter-validation" + implementation "org.springframework.boot:spring-boot-autoconfigure" + implementation "org.springframework.boot:spring-boot-starter" + implementation "org.springframework.boot:spring-boot-starter-oauth2-client" + implementation "org.apache.grails:grails-core" + implementation "org.springframework.boot:spring-boot-starter-actuator" + implementation "org.springframework.boot:spring-boot-starter-tomcat" + implementation "org.apache.grails:grails-web-boot" + implementation "org.apache.grails:grails-logging" + implementation "org.apache.grails:grails-rest-transforms" + implementation "org.apache.grails:grails-databinding" + implementation "org.apache.grails:grails-services" + implementation "org.apache.grails:grails-url-mappings" + implementation "org.apache.grails:grails-layout" + implementation "org.apache.grails:grails-interceptors" + implementation "org.apache.grails:grails-scaffolding" + implementation "org.apache.grails:grails-data-hibernate5" + implementation "org.apache.grails:grails-gsp" + integrationTestImplementation testFixtures("org.apache.grails:grails-geb") + profile "org.apache.grails.profiles:web" + runtimeOnly "org.fusesource.jansi:jansi" + runtimeOnly "com.h2database:h2" + runtimeOnly "com.zaxxer:HikariCP" + runtimeOnly "cloud.wondrify:asset-pipeline-grails" + testAndDevelopmentOnly platform(project(':grails-bom')) + testAndDevelopmentOnly "org.webjars.npm:bootstrap" + testAndDevelopmentOnly "org.webjars.npm:bootstrap-icons" + testAndDevelopmentOnly "org.webjars.npm:jquery" + testImplementation "org.apache.grails:grails-testing-support-datamapping" + testImplementation "org.spockframework:spock-core" + testImplementation "org.apache.grails:grails-testing-support-web" +} + +apply { + from rootProject.layout.projectDirectory.file('gradle/functional-test-config.gradle') + from rootProject.layout.projectDirectory.file('gradle/java-config.gradle') + from rootProject.layout.projectDirectory.file('gradle/test-webjar-asset-config.gradle') + from rootProject.layout.projectDirectory.file('gradle/grails-extension-gradle-config.gradle') +} \ No newline at end of file diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/advancedgrails.svg b/grails-test-examples/scaffolding/grails-app/assets/images/advancedgrails.svg new file mode 100644 index 00000000000..8b63ec8be50 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/images/advancedgrails.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/apple-touch-icon-retina.png b/grails-test-examples/scaffolding/grails-app/assets/images/apple-touch-icon-retina.png new file mode 100644 index 00000000000..d5bc4c0da0b Binary files /dev/null and b/grails-test-examples/scaffolding/grails-app/assets/images/apple-touch-icon-retina.png differ diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/apple-touch-icon.png b/grails-test-examples/scaffolding/grails-app/assets/images/apple-touch-icon.png new file mode 100644 index 00000000000..c3681cc467d Binary files /dev/null and b/grails-test-examples/scaffolding/grails-app/assets/images/apple-touch-icon.png differ diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/documentation.svg b/grails-test-examples/scaffolding/grails-app/assets/images/documentation.svg new file mode 100644 index 00000000000..29bc9d57d39 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/images/documentation.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/favicon.ico b/grails-test-examples/scaffolding/grails-app/assets/images/favicon.ico new file mode 100644 index 00000000000..76e4b11feda Binary files /dev/null and b/grails-test-examples/scaffolding/grails-app/assets/images/favicon.ico differ diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/grails-cupsonly-logo-white.svg b/grails-test-examples/scaffolding/grails-app/assets/images/grails-cupsonly-logo-white.svg new file mode 100644 index 00000000000..d3fe882c4bc --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/images/grails-cupsonly-logo-white.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/grails.svg b/grails-test-examples/scaffolding/grails-app/assets/images/grails.svg new file mode 100644 index 00000000000..79f698b698a --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/images/grails.svg @@ -0,0 +1,13 @@ + + + + grails + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/grails-test-examples/scaffolding/grails-app/assets/images/slack.svg b/grails-test-examples/scaffolding/grails-app/assets/images/slack.svg new file mode 100644 index 00000000000..34fcf4ce098 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/images/slack.svg @@ -0,0 +1,18 @@ + + + + slack_orange + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/grails-test-examples/scaffolding/grails-app/assets/javascripts/application.js b/grails-test-examples/scaffolding/grails-app/assets/javascripts/application.js new file mode 100644 index 00000000000..e59983fa3d6 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/javascripts/application.js @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// 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 webjars/jquery/3.7.1/dist/jquery.js +//= require webjars/bootstrap/5.3.7/dist/js/bootstrap.bundle +//= require_self + +if (typeof jQuery !== 'undefined') { + (function($) { + $('#spinner').ajaxStart(function() { + $(this).fadeIn(); + }).ajaxStop(function() { + $(this).fadeOut(); + }); + })(jQuery); +} \ No newline at end of file diff --git a/grails-test-examples/scaffolding/grails-app/assets/stylesheets/application.css b/grails-test-examples/scaffolding/grails-app/assets/stylesheets/application.css new file mode 100644 index 00000000000..c8b5f23f448 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/stylesheets/application.css @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* +* 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. +* +*= require webjars/bootstrap/5.3.7/dist/css/bootstrap +*= require webjars/bootstrap-icons/1.13.1/font/bootstrap-icons +*= require grails +*= require_self +*/ diff --git a/grails-test-examples/scaffolding/grails-app/assets/stylesheets/errors.css b/grails-test-examples/scaffolding/grails-app/assets/stylesheets/errors.css new file mode 100644 index 00000000000..18f7f30284a --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/stylesheets/errors.css @@ -0,0 +1,109 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +.filename { + font-style: italic; +} + +.exceptionMessage { + margin: 10px; + border: 1px solid #000; + padding: 5px; + background-color: #E9E9E9; +} + +.stack, +.snippet { + margin: 10px 0; +} + +.stack, +.snippet { + border: 1px solid #ccc; +} + +/* error details */ +.error-details { + border: 1px solid #FFAAAA; + background-color:#FFF3F3; + line-height: 1.5; + overflow: hidden; + padding: 10px 0 5px 25px; +} + +.error-details dt { + clear: left; + float: left; + font-weight: bold; + margin-right: 5px; +} + +.error-details dt:after { + content: ":"; +} + +.error-details dd { + display: block; +} + +/* stack trace */ +.stack { + padding: 5px; + overflow: auto; + height: 300px; +} + +/* code snippet */ +.snippet { + background-color: #fff; + font-family: monospace; +} + +.snippet .line { + display: block; +} + +.snippet .lineNumber { + background-color: #ddd; + color: #999; + display: inline-block; + margin-right: 5px; + padding: 0 3px; + text-align: right; + width: 3em; +} + +.snippet .error { + background-color: #fff3f3; + font-weight: bold; +} + +.snippet .error .lineNumber { + background-color: #faa; + color: #333; + font-weight: bold; +} + +.snippet .line:first-child .lineNumber { + padding-top: 5px; +} + +.snippet .line:last-child .lineNumber { + padding-bottom: 5px; +} \ No newline at end of file diff --git a/grails-test-examples/scaffolding/grails-app/assets/stylesheets/grails.css b/grails-test-examples/scaffolding/grails-app/assets/stylesheets/grails.css new file mode 100644 index 00000000000..b5b66c62538 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/assets/stylesheets/grails.css @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +table.scaffold tr>td:first-child, tr>th:first-child { + padding-left: 1.25em; +} + +table.scaffold tr>td:last-child, tr>th:last-child { + padding-right: 1.25em; +} + +table.scaffold th { + background-image: linear-gradient( + to bottom, + #ffffff 0%, + #f8f8f8 30%, + #eaeaea 70%, + #d4d4d4 100% + ); + border-bottom: 2px solid #b3b3b3; /* Adding a subtle shadow effect */ + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); /* Adding a drop shadow */ +} + +[data-bs-theme=dark] table.scaffold th { + background-image: linear-gradient( + to bottom, + #4a4a4a 0%, + #3e3e3e 30%, + #2a2a2a 70%, + #1e1e1e 100% + ); + border-bottom: 2px solid #141414; /* Adding a subtle shadow effect */ + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.3); /* Adding a drop shadow */ +} + +table.scaffold thead th { + white-space: nowrap; +} + +table.scaffold th a { + display: block; + text-decoration: none; +} + +table.scaffold th a:link, th a:visited { + color: #666666; +} + +table.scaffold th a:hover, th a:focus { + color: #333333; +} + +table.scaffold th.sortable a { + background-position: right; + background-repeat: no-repeat; + padding-right: 1.1em; +} + +table.scaffold th { + position: relative; +} + + +table.scaffold th.asc a:after { + content: '▲'; + position: absolute; + right: 10px; + font-size: 0.8em; +} + +table.scaffold th.desc a:after { + content: '▼'; + position: absolute; + right: 10px; + font-size: 0.8em; +} + +table.scaffold th:hover { + background: #f5f5f5 !important; +} \ No newline at end of file diff --git a/grails-test-examples/scaffolding/grails-app/conf/application.yml b/grails-test-examples/scaffolding/grails-app/conf/application.yml new file mode 100644 index 00000000000..d7a856590b1 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/conf/application.yml @@ -0,0 +1,157 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +--- +grails: + profile: web + codegen: + defaultPackage: com.example + gorm: + 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: + jmx: + unique-names: true + main: + banner-mode: "off" + groovy: + template: + check-template-location: false + devtools: + restart: + additional-exclude: + - '*.gsp' + - '**/*.gsp' + - '*.gson' + - '**/*.gson' + - 'logback-spring.xml' + - '*.properties' +environments: + development: + management: + endpoints: + enabled-by-default: true + web: + base-path: '/actuator' + exposure: + include: '*' + production: + management: + endpoints: + enabled-by-default: false + +--- +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 + scriptlet: html + taglib: none + staticparts: none + +--- +hibernate: + cache: + queries: false + use_second_level_cache: false + use_query_cache: false +dataSource: + pooled: true + jmxExport: true + driverClassName: org.h2.Driver + username: sa + password: '' + +environments: + development: + dataSource: + dbCreate: create-drop + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + test: + dataSource: + dbCreate: update + url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + production: + dataSource: + dbCreate: none + url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + 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 diff --git a/grails-test-examples/scaffolding/grails-app/conf/logback-spring.xml b/grails-test-examples/scaffolding/grails-app/conf/logback-spring.xml new file mode 100644 index 00000000000..9c781285ec6 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/conf/logback-spring.xml @@ -0,0 +1,59 @@ + + + + + + + false + + ${CONSOLE_LOG_THRESHOLD} + + + ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/grails-test-examples/scaffolding/grails-app/controllers/com/example/UrlMappings.groovy b/grails-test-examples/scaffolding/grails-app/controllers/com/example/UrlMappings.groovy new file mode 100644 index 00000000000..2f0d869449d --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/controllers/com/example/UrlMappings.groovy @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example + +class UrlMappings { + + static mappings = { + "/$namespace/$controller/$action?/$id?(.$format)?" {} + "/$controller/$action?/$id?(.$format)?"{} + + "/"(view:"/index") + "500"(view:'/error') + "404"(view:'/notFound') + } +} diff --git a/grails-test-examples/scaffolding/grails-app/controllers/com/example/UserController.groovy b/grails-test-examples/scaffolding/grails-app/controllers/com/example/UserController.groovy new file mode 100644 index 00000000000..fbe8526346b --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/controllers/com/example/UserController.groovy @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example + +import grails.plugin.scaffolding.RestfulServiceController +import grails.plugin.scaffolding.annotation.Scaffold + +@Scaffold(RestfulServiceController) +class UserController { +} diff --git a/grails-test-examples/scaffolding/grails-app/controllers/com/example/community/UserController.groovy b/grails-test-examples/scaffolding/grails-app/controllers/com/example/community/UserController.groovy new file mode 100644 index 00000000000..9ff317666ee --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/controllers/com/example/community/UserController.groovy @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example.community + +import grails.plugin.scaffolding.RestfulServiceController +import grails.plugin.scaffolding.annotation.Scaffold + +@Scaffold(RestfulServiceController) +class UserController { + static namespace = 'community' +} + diff --git a/grails-test-examples/scaffolding/grails-app/domain/com/example/User.groovy b/grails-test-examples/scaffolding/grails-app/domain/com/example/User.groovy new file mode 100644 index 00000000000..6ed3fb0df08 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/domain/com/example/User.groovy @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example + +import grails.gorm.annotation.AutoTimestamp +import org.springframework.security.core.GrantedAuthority +import org.springframework.security.core.authority.SimpleGrantedAuthority +import org.springframework.security.core.userdetails.UserDetails + +import java.time.LocalDateTime + +class User implements UserDetails { + + String firstName + String lastName + String email + String password + String roles = '' + + boolean accountNonExpired = true + boolean accountNonLocked = true + boolean credentialsNonExpired = true + boolean enabled = true + + @AutoTimestamp(AutoTimestamp.EventType.CREATED) LocalDateTime created + @AutoTimestamp LocalDateTime modified + + static constraints = { + firstName blank: false + lastName blank: false + email email: true, blank: false + password password:true + created nullable: true // Grails 7 Bug + modified nullable: true // Grails 7 Bug + } + + static mapping = { + table 'users' + } + + @Override + Collection getAuthorities() { + roles.split('').collect { new SimpleGrantedAuthority(it) } + } + + @Override + String getUsername() { email } +} diff --git a/grails-test-examples/scaffolding/grails-app/domain/com/example/community/User.groovy b/grails-test-examples/scaffolding/grails-app/domain/com/example/community/User.groovy new file mode 100644 index 00000000000..0916091cc00 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/domain/com/example/community/User.groovy @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example.community + +import grails.gorm.annotation.AutoTimestamp +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id + +import java.time.LocalDateTime + +@Entity(name = "CommunityUser") +class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + Long id + String firstName + String lastName + + @AutoTimestamp(AutoTimestamp.EventType.CREATED) LocalDateTime created + @AutoTimestamp LocalDateTime modified + + static constraints = { + firstName blank: false + lastName blank: false + created nullable: true // Grails 7 Bug + modified nullable: true // Grails 7 Bug + } + + static mapping = { + table 'community_users' + } +} diff --git a/grails-test-examples/scaffolding/grails-app/i18n/messages.properties b/grails-test-examples/scaffolding/grails-app/i18n/messages.properties new file mode 100644 index 00000000000..6d72d209d5d --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/i18n/messages.properties @@ -0,0 +1,71 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}] +default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL +default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number +default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address +default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}] +default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}] +default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}] +default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}] +default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}] +default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}] +default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation +default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}] +default.blank.message=Property [{0}] of class [{1}] cannot be blank +default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}] +default.null.message=Property [{0}] of class [{1}] cannot be null +default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique + +default.paginate.prev=Previous +default.paginate.next=Next +default.boolean.true=True +default.boolean.false=False +default.date.format=yyyy-MM-dd HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} created +default.updated.message={0} {1} updated +default.deleted.message={0} {1} deleted +default.not.deleted.message={0} {1} could not be deleted +default.not.found.message={0} not found with id {1} +default.optimistic.locking.failure=Another user has updated this {0} while you were editing + +default.home.label=Home +default.list.label={0} List +default.add.label=Add {0} +default.new.label=New {0} +default.create.label=Create {0} +default.show.label=Show {0} +default.edit.label=Edit {0} + +default.button.create.label=Create +default.button.edit.label=Edit +default.button.update.label=Update +default.button.delete.label=Delete +default.button.delete.confirm.message=Are you sure? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Property {0} must be a valid URL +typeMismatch.java.net.URI=Property {0} must be a valid URI +typeMismatch.java.util.Date=Property {0} must be a valid Date +typeMismatch.java.lang.Double=Property {0} must be a valid number +typeMismatch.java.lang.Integer=Property {0} must be a valid number +typeMismatch.java.lang.Long=Property {0} must be a valid number +typeMismatch.java.lang.Short=Property {0} must be a valid number +typeMismatch.java.math.BigDecimal=Property {0} must be a valid number +typeMismatch.java.math.BigInteger=Property {0} must be a valid number +typeMismatch=Property {0} is type-mismatched diff --git a/grails-test-examples/scaffolding/grails-app/init/com/example/Application.groovy b/grails-test-examples/scaffolding/grails-app/init/com/example/Application.groovy new file mode 100644 index 00000000000..4daaa0369ba --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/init/com/example/Application.groovy @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example + +import grails.boot.GrailsApp +import grails.boot.config.GrailsAutoConfiguration + +import groovy.transform.CompileStatic + +@CompileStatic +class Application extends GrailsAutoConfiguration { + static void main(String[] args) { + GrailsApp.run(Application, args) + } +} \ No newline at end of file diff --git a/grails-test-examples/scaffolding/grails-app/init/com/example/BootStrap.groovy b/grails-test-examples/scaffolding/grails-app/init/com/example/BootStrap.groovy new file mode 100644 index 00000000000..cc5b1a33e09 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/init/com/example/BootStrap.groovy @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example + +class BootStrap { + UserService userService + + def init = { + if (!userService.count()) { + User user = userService.save(new User(firstName: 'Test', lastName: 'User', email: 'test@grails.org', password: '{noop}letmein', roles: 'ROLE_USER')) + println("User created with username: ${user.email} password: ${user.password.replaceAll(/\{.+\}/, '')}") + } + } + + def destroy = { + } + +} diff --git a/grails-test-examples/scaffolding/grails-app/services/com/example/UserService.groovy b/grails-test-examples/scaffolding/grails-app/services/com/example/UserService.groovy new file mode 100644 index 00000000000..1362fc2a6c2 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/services/com/example/UserService.groovy @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example + +import grails.gorm.transactions.Transactional +import grails.plugin.scaffolding.GormService +import grails.plugin.scaffolding.annotation.Scaffold +import org.springframework.security.core.userdetails.UserDetails +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException + +@Scaffold(GormService) +@Transactional +class UserService implements UserDetailsService { + @Override + UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + def user = User.findByEmail(username) + if (!user) { + throw new UsernameNotFoundException("No user with email $username") + } + user + } +} diff --git a/grails-test-examples/scaffolding/grails-app/services/com/example/community/CommunityUserService.groovy b/grails-test-examples/scaffolding/grails-app/services/com/example/community/CommunityUserService.groovy new file mode 100644 index 00000000000..6acaa8400a0 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/services/com/example/community/CommunityUserService.groovy @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.example.community + +import grails.gorm.transactions.Transactional +import grails.plugin.scaffolding.annotation.Scaffold + +import grails.plugin.scaffolding.GormService + +@Scaffold(GormService) +@Transactional +class CommunityUserService {} diff --git a/grails-test-examples/scaffolding/grails-app/views/error.gsp b/grails-test-examples/scaffolding/grails-app/views/error.gsp new file mode 100644 index 00000000000..3df02aea997 --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/views/error.gsp @@ -0,0 +1,51 @@ +<%-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ https://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --%> + + + + <g:if env="development">Grails Runtime Exception</g:if><g:else>Error</g:else> + + + + +
+ + + + + + + + +
    +
  • An error has occurred
  • +
  • Exception: ${exception}
  • +
  • Message: ${message}
  • +
  • Path: ${path}
  • +
+
+
+ +
    +
  • An error has occurred
  • +
+
+
+ + diff --git a/grails-test-examples/scaffolding/grails-app/views/index.gsp b/grails-test-examples/scaffolding/grails-app/views/index.gsp new file mode 100644 index 00000000000..d0a31c90dca --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/views/index.gsp @@ -0,0 +1,102 @@ +<%-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ https://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --%> +<%@ page import="grails.util.Environment; org.springframework.core.SpringVersion; org.springframework.boot.SpringBootVersion" +%> + + + + Welcome to Grails + + + + + + + + + + +
+
+
+

Welcome to Grails

+ +

+ Congratulations, you have successfully started your first Grails application! At the moment + this is the default page, feel free to modify it to either redirect to a controller or display + whatever content you may choose. Below is a list of controllers that are currently deployed in + this application, click on each to execute its default action: +

+ + +
+
+
+ + + diff --git a/grails-test-examples/scaffolding/grails-app/views/layouts/main.gsp b/grails-test-examples/scaffolding/grails-app/views/layouts/main.gsp new file mode 100644 index 00000000000..81db64f17aa --- /dev/null +++ b/grails-test-examples/scaffolding/grails-app/views/layouts/main.gsp @@ -0,0 +1,99 @@ +<%-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ https://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --%> + + + + + + + <g:layoutTitle default="Grails"/> + + +