Necesito meter una app web que hice en JEE6 (.war) dentro de un .ear, para que éste además tenga fuentes de datos y otros recursos…
para ésto estoy usando un plug-in de Maven, el maven-ear-plugin. Con éste, por ejemplo, puedo incluír en el EAR al WAR y a un RAR
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ear-plugin</artifactId> <version>2.8</version> <configuration> <version>6</version><!-- JEE6 --> <modules> <!-- WAR --> <webModule> <groupId>com.numerica.grupo</groupId> <artifactId>miWar</artifactId> </webModule> <!-- RAR ActiveMQ --> <rarModule> <groupId>org.apache.activemq</groupId> <artifactId>activemq-rar</artifactId> </rarModule> </modules> <jboss><!--hasta JBoss 5--> <data-sources> <data-source></data-source> </data-sources> </jboss> </configuration> </plugin>
Al final hasta tiene un tag <jboss> donde puedo especificar fuentes de datos… aunque soporta hasta JBoss 5… habría que ver cómo se comporta con > 6.
Problemas de ClassLoader de Seam en un EAR
Sin embargo, el principal problema es que al intentar el deploy de este WAR (que, de por sí, funciona), escupe
WELD-001409 Ambiguous dependencies for type [SeamTransaction] with qualifiers [@DefaultTrans action] at injection point [[field] @Inject @DefaultTransaction private org.jboss.seam.faces.transaction.TransactionPhaseListener.transaction]. Possi ble dependencies [[Managed Bean [class org.jboss.seam.transaction.EntityTransaction] with qualifiers [@Any @DefaultTransaction], Managed Bean [class org.jboss.seam.transaction.DefaultSeamTransaction] with qualifiers [@Any @DefaultTransaction], Managed Bean [class org.jboss.seam.transaction.Hiberna teTransaction] with qualifiers [@Any @DefaultTransaction]]]
o también he obtenido
WELD-001408 Unsatisfied dependencies for type [ViewConfigExtension] with qualifiers [@Default] at injection point [[parameter 1] of [method] @Inject public org.jboss.seam.faces.view.config.ViewConfigStoreImpl.setup(ViewConfigExtension)]
Al parecer esto se debería a un problema del class loaders específico para WARs – que en el fondo cargan las clases de las librerías que vengan dentro de su WEB-INF/lib dentro de un ámbito al que sólo el WAR tiene acceso – aunque no termino de entender por qué esto daría dependencias ambiguas – en fín, véase >>>.
De hecho una de las maneras de ‘solucionarlo’ es desactivando este comportamiento – que pertenece a la especificación, por lo que no sé si me gusta la idea – en server/all/deployers/jbossweb.deployer/META-INF/war-deployers-jboss-beans.xml, donde habría que comentar lo siguiente
<!-- Allow for war local class loaders: in testing --> <bean name="WarClassLoaderDeployer"> <property name="relativeOrder">-1</property> <property name="filteredPackages">javax.servlet</property> </bean>
(que de hecho dice ‘in testing’…)
Además que la otra opción es que los JAR pertenezcan al EAR y no al WAR… lo cual tampoco me interesa porque sólo éste los utiliza.
No obstante, ésta no podría no ser una solución definitiva pues en tiempo de deploy igual se tira su
Error configurando escuchador de aplicación de clase org.jboss.weld.integration.webtier.jsp.JspInitializationListener: java.lang.LinkageError: loader constraint violation: when resolving overridden method "org.jboss.weld.integration.webtier.jsp.JspInitializationListener.contextInitialized(Ljavax/servlet/ServletContextEvent;)V" the class loader (instance of org/jboss/classloader/spi/base/BaseClassLoader) of the current class, org/jboss/weld/integration/webtier/jsp/JspInitializationListener, and its superclass loader (instance of org/jboss/classloader/spi/base/BaseClassLoader), have different Class objects for the type javax/servlet/ServletContextEvent used in the signature
Esto se debe a que dentro de mi WAR tengo algún JAR que pertenece al servidor (como bien me dieran la pista aquí >>). En efecto, si, revisando el contenido de mi WEB-INF/lib, elimino jboss-servlet-api_3.0_spec-1.0.0.Final.jar que era el más sospechoso, el error desaparece.
Ahora, ¿cómo hacer ésto desde maven para no estar eliminándolo a mano? Si ejecutamos
mvn dependency:tree
vemos que seam-security es el que lo está trayendo
+- org.jboss.seam.security:seam-security:jar:3.1.0.Final:runtime [INFO] | +- org.jboss.seam.persistence:seam-persistence:jar:3.1.0.Final:runtime [INFO] | | +- org.jboss.seam.persistence:seam-persistence-api:jar:3.1.0.Final:runtime [INFO] | | +- org.jboss.seam.transaction:seam-transaction-api:jar:3.1.0.Final:runtime [INFO] | | \- org.jboss.seam.transaction:seam-transaction:jar:3.1.0.Final:runtime [INFO] | | \- org.jboss.spec.javax.servlet:jboss-servlet-api_3.0_spec:jar:1.0.1.Final:runtime
por lo que podemos excluírlo desde la dependencia a este módulo así
<dependency> <groupId>org.jboss.seam.security</groupId> <artifactId>seam-security</artifactId> <version>3.1.0.Final</version> <scope>runtime</scope> <exclusions> <exclusion> <groupId>org.jboss.spec.javax.servlet</groupId> <artifactId>jboss-servlet-api_3.0_spec</artifactId> </exclusion> </exclusions> </dependency> <dependency>
El último error con el que me encontré fue con
java.lang.InstantiationException: org.jboss.seam.jsf.SeamApplicationFactory Unexpected exception when attempting to tear down the Mojarra runtime: java.lang.IllegalStateException: La aplicación no se ha inicializado correctamente durante el inicio, no se encuentra la fábrica: javax.faces.application.ApplicationFactory
lo que – al chunchún – se arregló poniéndole una dependencia a cdi-api.
<dependency> <groupId>javax.enterprise</groupId> <artifactId>cdi-api</artifactId> <version>1.0-SP2</version> <scope>provided</scope> </dependency>
Con ésto ya la aplicación deploya, así es que sí es una solución definitiva finalmente.
jboss-classloading.xml
Supuestamente hay otra forma de hacerlo, aunque al del link de arriba no le funcionó – que es meter en la carpeta WEB-INF/ del WAR un archivo jboss-classloading.xml el cual da una instrucción equivalente a lo que hicimos arriba (i.e. dejar el WAR en el classloader por default)
<?xml version="1.0" encoding="UTF-8"?> <classloading xmlns="urn:jboss:classloading:1.0" name="miApp-1.war" domain="DefaultDomain" top-level-classloader="true" export-all="NON_EMPTY" import-all="true"> </classloading>
Ojo que la propiedad name tiene que coincidir con el del WAR, lo cual puede ser una joda con los números de versión, etc. Aún así, me parece que ésta sería la solución de menor impacto, y por lo tanto la mejor en mi caso...
Sin embargo, efectivamente, sale el medio Bowser
java.lang.ClassNotFoundException: javax.faces.webapp.FacesServlet from BaseClassLoader
Es decir que reproducimos el ClassNotFoundException del link.
Ésta no es, por tanto, una opción viable.
Skinny WAR
Como dijimos, otra opción es sacar los JARs de la carpeta WEB-INF/lib en el WAR y ponerlos en una carpeta lib/ para todo el EAR. Ésto es una buena práctica si se tienen varios WARs en el EAR, ya que se evita duplicar las librerías que usen en común. Se le llama skinny war >>.
Para ésto usamos el maven-war-plugin :
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.3</version> <configuration> <!-- no dentro del WAR --> <packagingExcludes>WEB-INF/lib/*.jar</packagingExcludes> <archive> <manifest> <!-- en MANIFEST.MF --> <addClasspath>true</addClasspath> <!-- prefijo --> <classpathPrefix>lib/</classpathPrefix> </manifest> </archive> </configuration> </plugin>
Es decir, se exlcuyen las dependencias del WAR de la carpeta WEB-INF/lib, se añaden como referencias en el MANIFEST.MF para que las cargue del EAR, y se especifica que dentro de éste se deben buscar en la carpeta lib/.
Luego en el maven-ear-plugin hay que agregar las dependencias del WAR como dependencias del EAR también.
Y hay que declararlos como módulos
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ear-plugin</artifactId> <version>2.8</version> <configuration> <defaultLibBundleDir>lib/</defaultLibBundleDir> <!-- deja jars extra en lib/ --> <modules> <!-- WAR --> <webModule> <groupId>com.numerica.tests</groupId> <artifactId>miWar</artifactId> </webModule> <!-- librerias --> <jarModule> <groupId>org.jboss.seam.security</groupId> <artifactId>seam-security-api</artifactId> <includeInApplicationXml>true</includeInApplicationXml><!--ojo--> </jarModule> <!--etc--> </plugin>
Nótese que hay que ponerles includeInApplicationXml porque maven, por defecto, no lista los jars que no pertencen al proyecto.
Éso es, en teoría.
Sin embargo, así obtenemos un error de JSF en tiempo de deploy :
Excepción arrancando filtro Weld Conversation Propagation Filter: java.lang.ClassCastException: org.jboss.weld.servlet.ConversationPropagationFilter cannot be cast to javax.servlet.Filter
Por lo que entiendo, ésto sucede cuando la instancia de javax.servlet.Filter no se está cargando de jboss-javaee-6.0… por lo que puede estar relaciona con el problema de ClassLoaders (que lo cargue dos veces, en el WAR y en el EAR, por ejemplo).
Es por esto – y porque en otro post decía que podía haber un conflicto entre SeamSecurity y SeamFaces – que intenté dejar únicamente seam-faces en el WAR. Ésto usando packagingIncludes en lugar de ..Excludes en el maven-war-plugin
<packagingIncludes>WEB-INF/lib/seam-faces-3.1.0.Final.jar</packagingIncludes>
así ésta es la única librería que queda dentro del WAR.
Pero ésto nos lleva a un nuevo malo
WELD-000069 An interceptor must have at least one binding, but org.jboss.seam.faces.context.conversation.ConversationBoundaryInterceptor has none
¿o sea que como que no encuentra la librería? ésto considerando que ésta no se encuentra ni en lib/ del EAR ni en el MANIFEST.MF, sólo en WEB-INF/lib del WAR.
Bueno, como esto no resultó más sencillo y no nos interesa tener las librerías en el EAR porque nadie más las utiliza, y menos tener que estar duplicando las dependencias (en el WAR y el EAR) y declararlas como módulos, ni mucho menos tener que estar excluyendo algunas a mano, dejaremos esta veta inconclusa…
Es decir, hasta nuevo aviso, la única manera de deployar Seam 3 como EAR en JBoss 6 es deshabilitando la aislación de classloaders para WARs, como vimos inicialmente.