Pregunta

Tengo una aplicación de Grails 1.3.7. Estoy haciendo uso de las clases JMS de Spring para configurar uno de mis servicios de Grails como oyente de mensajes, configurando esas clases en grails-app / conf / resources.groovy. Estoy usando maven 2.0.9 para compilaciones, usando grails-maven-plugin 1.3.7 y el objetivo "maven-war" para crear un archivo war.

Tengo dos escenarios:

  1. Quiero poder ejecutar mi aplicación grails localmente, desde la línea de comandos, usando "mvn grails: run-app". Lo uso durante el desarrollo.
  2. Quiero poder ejecutar la aplicación dentro de JBoss 5.1.0 GA, mediante la implementación del archivo war creado por maven. Esto es lo que hacemos en nuestros entornos de integración, prueba y producción.

Cuando se ejecuta dentro de JBoss, todas las dependencias relacionadas con el proveedor de JMS están disponibles y proporcionadas por el servidor de aplicaciones. La forma normal de manejar esto con maven es declarar estas dependencias en el archivo pom, con el alcance de "proporcionado". Esto hará que estas dependencias estén disponibles para la compilación y las pruebas unitarias, pero las excluirá del archivo war.

Sin embargo, cuando ejecuto localmente desde la línea de comando usando "mvn grails: run-app", parece que estas dependencias no están disponibles para grails en tiempo de ejecución, como lo demuestran muchas ClassNotFound, etc. excepciones. Cambiar el alcance a "compilar" me permite ejecutar localmente. Sin embargo, ahora estas dependencias se empaquetan en mi archivo war, que no quiero y tiende a romper cosas cuando se ejecuta dentro de JBoss.

La solución (o solución alternativa) que he encontrado por ahora es incluir estas dependencias JMS con alcance predeterminado (compilación) en mi pom, y eliminar estos jar (y todas sus dependencias transitivas) del archivo war a través de algún código en BuildConfig .groovy (ver más abajo). Esto funciona, pero es complicado y propenso a errores porque tengo que enumerar cada dependencia transitiva (¡de las cuales hay muchas!).

Algunas otras cosas que he probado:

Al principio, pensé que quizás podría agregar las dependencias JMS requeridas a BuildConfig.groovy, en la sección "grails.project.dependency.resolution / dependencies", y dejarlas completamente fuera del pom. Sin embargo, esto no funcionó porque, según este enlace , la sección de dependencias de BuildConfig se ignora cuando se ejecuta grails en maven.

También noté la opción "pom true" (mencionada en el enlace anterior) y traté de usarla. Sin embargo, al intentar ejecutar grails: run-app, grails arroja advertencias sobre dependencias no resueltas y da un error de Tomcat:

:::: WARNINGS
::::::::::::::::::::::::::::::::::::::::::::::
::          UNRESOLVED DEPENDENCIES         ::
::::::::::::::::::::::::::::::::::::::::::::::
:: commons-collections#commons-collections;3.2.1: configuration not found in commons-collections#commons-collections;3.2.1: 'master'. It was required from org.grails.internal#load-manager-grails;1.2-SNAPSHOT compile
:: org.slf4j#slf4j-api;1.5.8: configuration not found in org.slf4j#slf4j-api;1.5.8: 'master'. It was required from org.grails.internal#load-manager-grails;1.2-SNAPSHOT runtime

...

java.lang.LinkageError: loader constraint violation: when resolving overridden method "org.apache.tomcat.util.digester.Digester.setDocumentLocator(Lorg/xml/sax/Locator;)V" the class loader (instance of org/codehaus/groovy/grails/cli/support/GrailsRootLoader) of the current class, org/apache/tomcat/util/digester/Digester, and its superclass loader (instance of <bootloader>), have different Class objects for the type org/xml/sax/Locator used in the signature
        at org.grails.tomcat.TomcatServer.start(TomcatServer.groovy:212)

Mi pregunta: ¿Existe una mejor manera, a través de grails y / o opciones de configuración de maven, para lograr lo que quiero, es decir, poder ejecutar grails con éxito localmente y dentro de JBoss, sin tener que excluir manualmente todas las dependencias transitivas del archivo war?

Nota: no puedo cambiar la versión de grails, JBoss o maven que estoy usando.

Algunos extractos de archivos relevantes:

BuildConfig.groovy:

grails.project.class.dir = "target/classes"
grails.project.test.class.dir = "target/test-classes"
grails.project.test.reports.dir = "target/test-reports"
grails.project.war.file = "target/${appName}-${appVersion}.war"

grails.project.dependency.resolution = {
    // inherit Grails' default dependencies
    inherits("global") {
        // uncomment to disable ehcache
        // excludes 'ehcache'
    }
    log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose'
    repositories {
        // only use our internal Archiva instance
        mavenRepo "http://my-company.com/archiva/repository/mirror"
        mavenLocal()
    }
    dependencies {
        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.
    }

    //Remove own log4j and use the one supplied by JBoss instead
    grails.war.resources = {stagingDir ->
        delete file:"$stagingDir/WEB-INF/classes/log4j.properties" // logging conf done in JBoss only

        def files = fileScanner {
            fileset(dir:"$stagingDir/WEB-INF/lib"){
                [
                    // all of the following are jms-related dependencies supplied by JBoss
                    /* org.jboss.javaee */ "jboss-jms-api-*.jar",
                    /* org.jboss.naming */ "jnp-client-*.jar",
                    /*    org.jboss */ "jboss-common-core-*.jar",
                    /*    org.jboss.logging */ "jboss-logging-spi-*.jar",
                    /* jboss.messaging */ "jboss-messaging-*.jar",
                    /* org.jboss.aop */ "jboss-aop-*.jar",
                    /*    org.apache.ant */ "ant-*.jar",
                    /*        org.apache.ant */ "ant-launcher-*.jar",
                    /*    org.jboss */ "jboss-reflect-*.jar",
                    /*    org.jboss */ "jboss-mdr-*.jar",
                    /*    qdox */ "qdox-*.jar",
                    /*    trove */ "trove-*.jar",
                    /*    org.jboss.logging */ "jboss-logging-log4j-*.jar",
                    /* org.jboss.remoting */ "jboss-remoting-*.jar",
                    /* jboss */ "jboss-serialization-*.jar",
                    /* oswego-concurrent */ "concurrent-*.jar",
                    /* org.jboss.jbossas */ "jboss-as-cluster-*-jboss-ha-legacy-client.jar",
                    /*    commons-logging */ "commons-logging-*.jar",
                    /*    org.jboss.jbossas */ "jboss-as-server-*.jar",
                    /*       sun-jaxb */ "jaxb-api-*.jar",
                    /*       org.jboss.jbossas */ "jboss-as-deployment-*.jar",
                    /*          org.jboss.javaee */ "jboss-jad-api-*.jar",
                    /*          org.jboss.security */ "jboss-security-spi-*.jar",
                    . . . // and the other 74 transitive dependencies...
                ].each{
                    include(name:it)
                }
            }
        }
        files.each
        {
            delete(file: it)
        }

    }
}

pom.xml:

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

    . . .

    <dependencies>

        . . .

        <dependency>
            <!-- already a dep of grails-crud; make it scope:compile for resources.groovy -->
            <groupId>org.springframework</groupId>
            <artifactId>spring-jms</artifactId>
            <version>3.0.5.RELEASE</version>
        </dependency>
        <!-- Note: all the remaining jms dependencies below should be 'provided' scope, but
             grails doesn't correctly pull them in when running locally, so we must leave
             them as compile scope and remove them (and their transitive dependencies) from
             the war file through BuildConfig.groovy
        -->
        <dependency>
            <groupId>org.jboss.javaee</groupId>
            <artifactId>jboss-jms-api</artifactId>
            <version>1.1.0.GA</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.naming</groupId>
            <artifactId>jnp-client</artifactId>
            <version>5.0.3.GA</version>
        </dependency>
        <dependency>
            <groupId>jboss.messaging</groupId>
            <artifactId>jboss-messaging</artifactId>
            <version>1.4.3.GA</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.aop</groupId>
            <artifactId>jboss-aop</artifactId>
            <version>2.1.1.GA</version>
            <classifier>client</classifier>
            <exclusions>
                <exclusion>
                    <!-- see http://jira.codehaus.org/browse/GROOVY-3356 -->
                    <groupId>apache-xerces</groupId>
                    <artifactId>xml-apis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.jboss.remoting</groupId>
            <artifactId>jboss-remoting</artifactId>
            <version>2.5.3.SP1</version>
        </dependency>
        <dependency>
            <groupId>jboss</groupId>
            <artifactId>jboss-serialization</artifactId>
            <version>1.0.3.GA</version>
        </dependency>
        <dependency>
            <groupId>oswego-concurrent</groupId>
            <artifactId>concurrent</artifactId>
            <version>1.3.4-jboss-update1</version>
        </dependency>
        <!-- the following two are required in order to connect to HA-JNDI -->
        <dependency>
            <groupId>org.jboss.jbossas</groupId>
            <artifactId>jboss-as-cluster</artifactId>
            <classifier>jboss-ha-legacy-client</classifier>
            <version>5.1.0.GA</version>
        </dependency>
        <dependency> 
            <groupId>org.jboss.cluster</groupId>
            <artifactId>jboss-ha-client</artifactId>
            <version>1.1.1.GA</version>
        </dependency>

        <!-- End dependencies for connecting to JMS -->

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.grails</groupId>
                <artifactId>grails-maven-plugin</artifactId>
                <version>1.3.7</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <goals>
                            <goal>init</goal>
                            <goal>maven-clean</goal>
                            <goal>validate</goal>
                            <goal>config-directories</goal>
                            <goal>maven-compile</goal>
                            <goal>maven-test</goal>
                            <goal>maven-war</goal>
                            <goal>maven-functional-test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

resources.groovy:

import org.codehaus.groovy.grails.commons.ConfigurationHolder
import org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter
import org.springframework.jms.listener.DefaultMessageListenerContainer
import org.springframework.jms.listener.adapter.MessageListenerAdapter
import org.springframework.jms.support.destination.JndiDestinationResolver
import org.springframework.jndi.JndiObjectFactoryBean
import org.springframework.jndi.JndiTemplate

beans = {

    def config = ConfigurationHolder.config

    jndiTemplate(JndiTemplate) {
        environment = config.myQueue.ctx.toProperties() // flattens a{b{c}} to 'a.b.c'
    }

    jmsFactory(JndiObjectFactoryBean) {
        jndiTemplate = jndiTemplate
        jndiName = config.myQueue.connectionFactory as String
        lookupOnStartup = false // need this?
        proxyInterface = "javax.jms.ConnectionFactory"
    }

    authJmsFactory(UserCredentialsConnectionFactoryAdapter) {
        targetConnectionFactory = jmsFactory
        username = config.app.auth.username as String
        password = config.app.auth.password as String
    }

    destinationResolver(JndiDestinationResolver) {
        jndiTemplate = jndiTemplate
    }

    jmsMessageListener(MessageListenerAdapter, ref("myMessageDrivenService")) {
        defaultListenerMethod = "onEventMessage"
    }

    jmsContainer(DefaultMessageListenerContainer) {
        connectionFactory = authJmsFactory
        destinationResolver = destinationResolver
        destinationName = config.eventQueue.queueName as String
        messageListener = jmsMessageListener
        transactionManager = ref("transactionManager") // grails' txn mgr
        cacheLevel = DefaultMessageListenerContainer.CACHE_CONNECTION
        autoStartup = false // started up in Bootstrap.groovy
    }
}
¿Fue útil?

Solución

La solución para esto es crear un alcance dinámico para su dependencia a través de un perfil.

Ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

. . .
<properties>
   <jms.deps.scope>compile</jms.deps.scope>
</properties>

<profile>
   <id>build</id>
   <properties>
       <jms.deps.scope>provided</jms.deps.scope>
   </properties>
</profile>

<dependencies>
   <dependency>
      <groupId>whatever</groupId>
      <artifactId>whatever</artifactId>
      <scope>${jms.deps.scope}</scope>
   </dependency>
</dependencies>

. . .

Luego, cuando su línea de comando especificará el perfil:

mvn clean install war -P build

Espero que esto ayude.

Otros consejos

Esto se ha "solucionado" en Grails 2.0.El complemento maven para grails se ha actualizado para que el alcance "proporcionado" signifique que la dependencia está disponible cuando se ejecuta localmente, pero no está incluida en el archivo war del paquete.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top