Skip to content

Instantly share code, notes, and snippets.

@tychobrailleur
Last active January 16, 2018 10:06
Show Gist options
  • Save tychobrailleur/f1557cb8771b771d5632 to your computer and use it in GitHub Desktop.
Save tychobrailleur/f1557cb8771b771d5632 to your computer and use it in GitHub Desktop.
Grails Notes

Bootstrap Scripts

## Grails script, startGrails Grails main class in startGrails is org.codehaus.groovy.grails.cli.support.GrailsStarter that sets up config to execute org.codehaus.groovy.grails.cli.GrailsScriptRunner to run Groovy scripts. Executes the Groovy script, in interactive mode if no script invoked. ~/.grails/.aliases may contain command aliases.

Startup

RunApp.groovy -> _GrailsRun.groovy instantiates GrailsProjectRunner (org.codehaus.groovy.grails.project.container.GrailsProjectRunner in grails-project-api). Also calls _GrailsWar that calls _GrailsClean and _GrailsPackage.

_GrailsPackage.groovy packageApp calls:

  • compile from _GrailsCompile.groovy that compiles Groovy and Java sources (“Packaging Grails application”).
  • projectPackager.packageApplication() from class org.codehaus.groovy.grails.project.packaging.GrailsProjectPackager
  • configureServerContextPath that calls projectPackager.configureServerContextPath()
  • loadPlugins() from _PluginDependencies:

GrailsProjectPluginLoader#loadPlugins() calls grailsApplication.initialise(), described later.

  • generateWebXml that calls projectPackager.generateWebXml(pluginManager).

Then runApp from _GrailsRun is called, that calls GrailsProjectRunner#runApp(). GrailsProjectRunner is a subclass of BaseSettingsApi. runApp() calls runInline which creates a server factory (by default org.grails.plugins.tomcat.TomcatServerFactory) and creates a EmbeddableServer server instance to run the server (GrailsProjectRunner#runServer) as an expolded war.

Once the server started, a StatusFinal event is triggered.

web.xml

Prepares webapp; web.xml defines listener org.codehaus.groovy.grails.web.context.GrailsContextLoaderListener and Grails dispatcher servlet org.codehaus.groovy.grails.web.servlet.GrailsDispatcherServlet. It also defines Spring applicationContext.xml

Spring Bootstrapping

GrailsDispatcherServlet is a Spring MVC DispatcherServlet. createWebApplicationContext sets up the application context, executes Grails bootstrap (GrailsConfigUtils.executeGrailsBootstraps). The parent application context is in web-app/WEB-INF/applicationContext.xml. Grails application is in that xml: GrailsApplicationFactoryBean.

setApplication(parent.getBean(GrailsApplication.APPLICATION_ID, GrailsApplication.class))

It gets a GrailsWebApplicationContext, sets as webContext, and apply initializers.

Grails Domain Classes

From grails-core:

  • GrailsProjectPluginLoader#loadPlugins() calls grailsApplication.initialise().

  • Domain classes get wired in: org.codehaus.groovy.grails.commons.DefaultGrailsApplication#initialise(), configureLoadedClasses, initArtefactHandlers

  • Also GrailsContextLoaderListener calls initWebApplicationContext which inits the Spring web application context, and in turn calls DefaultGrailsApplication#initialise().

  • DefaultGrailsApplication#initArtefactHandlers -> DomainClassArtefactHandler, DefaultGrailsDomainClass, DefaultGrailsDomainClassProperty, DynamicFinderTypeCheckingExtension, GrailsCompileStatic, EntityASTTransformation

GlobalGrailsClassInjectorTransformation: A global transformation that applies Grails' transformations to classes within a Grails project

META-INF/grails.factories service for factories.

  • AbstractGrailsArtefactTransformer -> Use GrailsASTUtils performInstanceImplementationInjection, performStaticImplementationInjection

  • DefaultGrailsDomainClassInjector: injects id, version and toString()

  • DynamicFinderTypeCheckingExtension

makeDynamicGormCall -> AbstractTypeCheckingExtension#makeDynamic in Groovy language "Used to instruct the type checker that the call is a dynamic method call."

See GroovyTypeCheckingExtensionSupport WhereQueryTypeCheckingExtension.groovy

ArtefactHandler

ArtefactHandlerAdapter in grails-core is the base class for handlers that manage Grails class types (such as controllers, services, etc.). For example, org.codehaus.groovy.grails.commons.ServiceArtefactHandler, org.codehaus.groovy.grails.commons.ControllerArtefactHandler, etc.

Dynamic finder

  • DynamicAdvisedInterceptor
  • invoke
  • invokeWithinTransaction
  • cleanupTransactionInfo
  • restoreThreadLocalStatus
  • commitTransactionAfterReturning
  • grails-datastore-gorm: dynamic finder gets translated into a Hibernate criteria.

GrailsDomainClassHibernateEntity bridges the GrailsDomainClass interface into the PersistentEntity interface.

In grails-datastore-gorm, org.grails.datastore.gorm.GormStaticApi.groovy contains the methods that get automatically added to domain classes:

    def withSession(Closure callable) {
        execute ({ Session session ->
            callable.call session
        } as SessionCallback)
    }

    def withNewTransaction(Map transactionProperties, Closure callable) {
        def props = new HashMap(transactionProperties)
        props.remove 'propagationName'
        props.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
        withTransaction(props, callable)
    }

    def withTransaction(TransactionDefinition definition, Closure callable) {
        Assert.notNull transactionManager, "No transactionManager bean configured"

        if (!callable) {
            return
        }

        new GrailsTransactionTemplate(transactionManager, definition).execute(callable)
    }

    def withNewSession(Closure callable) {
        def session = datastore.connect()
        try {
            DatastoreUtils.bindNewSession session
            return callable?.call(session)
        }
        finally {
            DatastoreUtils.unbindSession session
        }
    }

DatastoreUtils in org/grails/datastore/mapping/core/DatastoreUtils.java in grails-datastore-core provides most of the facilities for handling sessions and executing callbacks, passing session:

    public static Session bindNewSession(final Session session) {
        SessionHolder sessionHolder = (SessionHolder)TransactionSynchronizationManager.getResource(session.getDatastore());
        if (sessionHolder == null) {
            return bindSession(session);
        }

        sessionHolder.addSession(session);
        return session;
    }

    public static <T> T execute(final Datastore datastore, final SessionCallback<T> callback) {
        boolean existing = datastore.hasCurrentSession();
        Session session = existing ? datastore.getCurrentSession() : bindSession(datastore.connect());
        try {
            return callback.doInSession(session);
        }
        finally {
            if (!existing) {
                TransactionSynchronizationManager.unbindResource(session.getDatastore());
                closeSessionOrRegisterDeferredClose(session, datastore);
            }
        }
    }

org.codehaus.groovy.grails.orm.hibernate.HibernateGormEnhancer is called when the Hibernate4 plugin is initialized and its doWithDynamicMethods closure from Hibernate4GrailsPlugin is called.

org.codehaus.groovy.grails.orm.hibernate.HibernateGormStaticApi is used when using Hibernate. GORM calls are based on org.codehaus.groovy.grails.orm.hibernate.GrailsHibernateTemplate (grails-datastore-orm-hibernate4), which do a getSession on a sessionFactory (of type org.codehaus.groovy.grails.orm.hibernate.SessionFactoryProxy):

    public Session getCurrentSession() throws HibernateException {
        return getCurrentSessionFactory().getCurrentSession();
    }

    public SessionFactory getCurrentSessionFactory() {

        SessionFactory sf = applicationContext.getBean(targetBean, SessionFactoryHolder.class).getSessionFactory();

        if (!sf.equals(currentSessionFactory)) {
            currentSessionFactory = sf;
            updateCurrentSessionContext(sf);
        }

        return currentSessionFactory;
    }

currentSession() calls GrailsSessionContext.currentSession() to “retrieve the Spring-managed Session for the current thread, if any.”. This calls org.springframework.transaction.support.TransactionSynchronizationManager.getResource(sessionFactory);. If no session found, a HibernateException("No Session found for current thread") is thrown.

org.codehaus.groovy.grails.plugins.web.api.ControllersApi in the grails-plugin-controllers project calls bindData as part of the initializeCommandObject. bindData then calls DataBindingUtils#bindObjectToDomainInstance in the grails-web-databinding project, and the following happens:

                final DataBindingSource bindingSource = createDataBindingSource(grailsApplication, object.getClass(), source);
                final DataBinder grailsWebDataBinder = getGrailsWebDataBinder(grailsApplication);
                grailsWebDataBinder.bind(object, bindingSource, filter, include, exclude);

The DataBinder is retrieved from the main context (DATA_BINDER_BEAN_NAME = 'grailsWebDataBinder'). It is initialized by DataBindingGrailsPlugin when the app starts up as a org.codehaus.groovy.grails.web.binding.GrailsWebDataBinder instance.

GrailsWebDataBinder is a SimpleDataBinder, and as such uses registerConverter to add converters depending on the target type.

xmlDataBindingSourceCreator is also registered as a spring bean by DataBindingGrailsPlugin. It uses XmlSlurper to parse the incoming XML:

    @Override
    protected DataBindingSource createBindingSource(Reader reader) {
        def gpath = IOUtils.createXmlSlurper().parse(reader)
        def gpathMap = new GPathResultMap(gpath)
        return new SimpleMapDataBindingSource(gpathMap)
    }

It is called in DataBindingUtils (cf. above) as follows:

    public static DataBindingSource createDataBindingSource(GrailsApplication grailsApplication, Class bindingTargetType, Object bindingSource) {
        final DataBindingSourceRegistry registry = getDataBindingSourceRegistry(grailsApplication);
        final MimeType mimeType = getMimeType(grailsApplication, bindingSource);
        return registry.createDataBindingSource(mimeType, bindingTargetType, bindingSource);
    }

Finding Mime Type of request

org.codehaus.groovy.grails.web.mime.MimeTypeResolver (grails-web-common) is used to resolve the mime type (org.codehaus.groovy.grails.web.mime.DefaultMimeTypeResolver from grails-plugin-mimetypes):

    @Override
    MimeType resolveRequestMimeType(GrailsWebRequest webRequest = GrailsWebRequest.lookup()) {
        if (webRequest != null) {
            final allMimeTypes = requestMimeTypesApi.getMimeTypes(webRequest.request)
            if(allMimeTypes) {
                return allMimeTypes[0]
            }
        }
        return null
    }

RequestMimeTypesApi defines getMimeTypes as follows:

    /**
     * Obtains a list of configured {@link MimeType} instances for the request
     *
     * @param request The request
     * @return A list of configured mime types
     */
    MimeType[] getMimeTypes(HttpServletRequest request) {
        MimeType[] result = (MimeType[])request.getAttribute(GrailsApplicationAttributes.REQUEST_FORMATS)
        if (!result) {
            def parser = new DefaultAcceptHeaderParser(getMimeTypes())
            def header = request.contentType
            if (!header) header = request.getHeader(HttpHeaders.CONTENT_TYPE)
            result = parser.parse(header, header ? new MimeType(header) : MimeType.HTML)

            request.setAttribute(GrailsApplicationAttributes.REQUEST_FORMATS, result)
        }
        result
    }

GrailsApplicationAttributes:

    String REQUEST_FORMATS = "org.codehaus.groovy.grails.REQUEST_FORMATS";

DataBindingSourceRegistry

DataBindingSourceRegistry is in grails-web-common, and its default implementation is org.codehaus.groovy.grails.web.binding.bindingsource.DefaultDataBindingSourceRegistry from grails-web-databinding

    @PostConstruct
    void initialize() {
        registerDefault(MimeType.JSON, new JsonDataBindingSourceCreator())
        registerDefault(MimeType.TEXT_JSON, new JsonDataBindingSourceCreator())
        registerDefault(MimeType.XML, new XmlDataBindingSourceCreator())
        registerDefault(MimeType.TEXT_XML, new XmlDataBindingSourceCreator())
    }

ClassAndMimeTypeRegistry (superclass) providers the registerDefault method:

    void registerDefault(MimeType mt, R object) {
        defaultObjectsByMimeType.put(mt, object)
    }
    @Override
    DataBindingSource createDataBindingSource(MimeType mimeType, Class bindingTargetType, bindingSource) {
        def helper = getDataBindingSourceCreator(mimeType, bindingTargetType, bindingSource)
        helper.createDataBindingSource(mimeType, bindingTargetType, bindingSource)
    }

The registry is retrieved as follows in DataBindingUtils:

    public static DataBindingSourceRegistry getDataBindingSourceRegistry(GrailsApplication grailsApplication) {
        DataBindingSourceRegistry registry = null;
        if(grailsApplication != null) {
            ApplicationContext context = grailsApplication.getMainContext();
            if(context != null) {
                if(context.containsBean(DataBindingSourceRegistry.BEAN_NAME)) {
                    registry = context.getBean(DataBindingSourceRegistry.BEAN_NAME, DataBindingSourceRegistry.class);
                }
            }
        }
        if(registry == null) {
            registry = new DefaultDataBindingSourceRegistry();
        }

        return registry;
    }

Grails 2.4.4 is a Spring MVC 4.0.7 app using Groovy 2.3.7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment