Posts Tagged ‘Glassfish’

h1

Tuning de Glassfish 2.1: el baseline

02/26/2010

Wizdoc [Icon By Buuf]  Tips & Tricks.
Cuando realizamos un tuning o puesta a punto para mejorar el desempeño de una aplicación, muchos de nosotros no sabemos por donde empezar. Con anterioridad hemos visto que lo mejor es iniciar tuning del código aplicativo, pues muchas veces es lo más fácil o que mayor impacto logra en el desempeño de la solución, para después ir bajando a las capas más abstractas de la arquitectura (contenedores de aplicaciones o middleware, luego sistema operativo y finalmente hardware). Ahora bien, con bastante regularidad, la mayoría de los encargados de hacer despliegues de aplicaciones saben qué parámetros modificar para hacer que la Máquina Virtual de Java (JVM) dé lo mejor de sí, pero en cuanto al servidor de aplicaciones, todos se quedan en blanco.

Hace poco ayudé a unos compañeros con este tipo de parámetros, pues su aplicación se caía con tan sólo un par de deployments, marcando el temido java.lang.OutOfMemoryError en muy poco tiempo. Entonces, aquí presento los parámetros más socorridos para mejorar el desempeño de una aplicación, con pequeños ejemplos para el servidor de aplicaciones Glassfish 2.1. No son en absoluto exhaustivos, pero pueden proporcionarnos una línea base a partir de la cual podemos ir trabajando:

1. La versión de la Máquina Virtual de Java

Obviamente es un cliché, pero hemos comprobado la diferencia que puede hacer una versión de Java. Siempre es una buena práctica instalar la última versión en los servidores donde correrá la aplicación. No es necesario contar con el root del servidor si se instala mediante un ejecutable (por ejemplo, j2sdk-1_6_0_18-solaris-sparc.sh) lo que permite instalar una nueva versión de Java sin impactar dominios de aplicaciones desplegados previamente. Primero, hay que instalar la nueva versión de la JVM y luego deberemos cambiar el path desde donde la toma Glassfish:

$GLASSFISH_HOME/config/asenv.conf
propiedad a modificar: AS_JAVA=<java_installations>/j2sdk1.6.0_18

2. Modo de ejecución de la Máquina Virtual de Java

Los servidores Glassfish corren por default en modo "cliente". Esto hace que levanten más rápido en un ambiente de desarrollo, pero esta modalidad de ejecución no es ideal en producción. Adicionalmente, por default la JVM tiene un límite de consumo de memoria de 64 Megabytes. Si nuestro servidor productivo tiene más que eso, lo mejor es incrementar los valores de acuerdo al disponible de memoria con el que contamos:

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar > JVM Settings > JVM Options.
• Editar las opciones de la JVM o agregar nuevas en el campo de texto correspondiente: -server -Xmx1500m -Xms1500m
• Click en Save del lado derecho.
• Reiniciar el servidor.

En el ejemplo Xms es la cantidad de memoria inicial (aquí se definieron 1500 MB o 1.5 Gigabytes) y Xmx es la cantidad de memoria máxima a usar por la JVM.

3. Número de threads de acceso

Los threads de acceso (acceptor threads) son hilos de ejecución asignados a un socket para que el servidor procese peticiones HTTP. El número por default es uno, pero es mejor designar el número de threads de acuerdo al número de cores (núcleos de procesador) que tiene la máquina donde corre el servidor, usando una proporción de 1 thread por cada 1 a 4 cores. Puede ser necesario probar la aplicación con diferentes parámetros para encontrar el óptimo. Ejemplo:

Una M3000 con 2 procesadores dual core puede tener un mínimo de (2 CPUs x 2 dual cores / 4 threads por core) = 1 thread o un máximo de (2 CPUs x 2 dual cores / 1 thread por core) = 4 threads.

Para modificar los valores en Glassfish, seguimos los siguientes pasos:

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar: Configuration > HTTP Service > HTTP Listeners
• Click en http-listener-1
• Editar el campo Acceptor Threads en la sección Advanced
• Click en Save del lado derecho.
• Reiniciar el servidor.

4. Número de threads de procesamiento

Los threads de procesamiento son los que ejecutan las peticiones HTTP. El default es 5, pero debe modificarse de acuerdo al número de cores que posee la máquina en proporción 1 a 1. Si la aplicación ocupa mucho uso de disco (I/O), es recomendable que el valor sea multiplicado por dos. Ejemplo:

Una M3000 con 2 dual cores puede tener 2 CPUs x 2 dual cores = 4 threads; si la aplicación usa mucho I/O, el número se multiplica por dos, quedando en 8 threads.

Nuevamente, para implementar este cambio, podemos seguir los siguientes pasos:

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar: Configuration > HTTP Service > Tab RequestProcessing
• Cambiar el valor Thread Count al número correspondiente.
• Click en Save del lado derecho.
• Reiniciar el servidor.

5. Subsistema Keep-alive

El keep-alive se utiliza para optimizar el uso de red. Normalmente, una petición HTTP requiere abrir la conexión – enviar datos – cerrar la conexión. Pero si una misma conexión envía información en diferentes tiempos, ¿no sería mejor dejarla siempre abierta para no pasar por el trabajoso proceso de abrirla? Siempre se recomienda cambiar el número de threads a 1 por cada 8 cores en la máquina para mejorar el uso de estos recursos. Ejemplo:

Una M3000 con 2 dual cores puede tener 2 CPUs x 2 cores = 4; por default debe tener 1 thread asignado. Si dicha M3000 tuviera 4 CPUs quad core, tendría 2 CPUs x 4 cores = 16; un default de 2 threads asignados.

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar: Configuration > HTTP Service > Tab KeepAlive
• Editar campo thread-count.
• Click en Save del lado derecho.
• Reiniciar el servidor.

6. Cacheo de archivos estáticos

En caso de utilizar muchos archivos estáticos en la aplicación (HTML, imágenes) es necesario cambiar los valores por default de acuerdo al tamaño promedio de estos archivos. Nota: es necesario calcular estos valores previamente a su ingreso al servidor para compararlo con el default, que en la mayoría de las veces es más que suficiente. Esto porque el incremento de cache aumenta los requerimientos de memoria llegando incluso a bloquear el servidor. Ejemplo:

La aplicación tiene 2,000 HTMLs estáticos con un tamaño promedio de 600 KB cada uno y 4,000 imágenes con un tamaño promedio de 200 KB cada una, entonces se requerirían los siguientes parámetros:

Globally = Enabled
MaxFilesCount = 6000
MediumFileSizeLimit = 614400
MediumFileSize = 1228800000 (= 614,400 bytes x 2,000 = 1.14 GigaBytes)
SmallFileSizeLimit = 204800
SmallFileSize = 819200000 (= 204,800 bytes x 4,000 = 781.25 MegaBytes)
FileCacheEnabling = On

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar: Configuration > HTTP Service > Tab HTTP File Cache
• Habilitar cache globalmente (primer select box)
• Cambiar tamaños de propiedades relacionadas (ver ejemplo)
• Click en Save del lado derecho.
• Reiniciar el servidor.

7. Deshabilitar log de acceso

Siempre se recomienda deshabilitar el log de acceso HTTP en un despliegue productivo, especialmente si hay un web server como front/proxy para la plataforma – pues ya tendrá su propio log – o existe mucho I/O por parte de la aplicación que estaría compitiendo por uso de disco.

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar: Configuration > HTTP Service
• Deshabilitar casilla Access Logging
• Click en Save del lado derecho.
• Reiniciar el servidor.

8. default-web.xml

Una vez que hayamos decidido subir el desarrollo a producción, ya no es necesario que el servidor esté corriendo un demonio que verifique si los JSP han cambiado para recompilarlos. Por otro lado, cuando se compila un JSP, siempre se requiere usar el método toCharArray para parsear cadenas estáticas, usadas normalmente en etiquetas HTML y JSP. Si desde el principio indicamos que estas cadenas deben ser arreglos de caracteres, evitamos la llamada constante a dicho método.

Para modificar este comportamiento, editamos el archivo default-web.xml, agregando las siguientes etiquetas al cuerpo del XML:

<init-param>
  <param-name>development</param-name>
  <param-value>false</param-value>
</init-param>
<init-param>
  <param-name>genStrAsCharArray</param-name>
  <param-value>true</param-value>
</init-param>

El archivo se encuentra generalmente localizado en $GLASSFISH_HOME/domains/domain1/config/default-web.xml

9. Configuración de JDBC

Siempre es deseable optimizar las conexiones de JDBC. Agregando los siguientes parámetros a la configuración de la misma en Glassfish, logramos parte del cometido:

Parámetros para Oracle:

ImplicitCachingEnabled=true
MaxStatements=200

Parámetros para MySQL:

cachePrepStmts=true
prepStmtCacheSize=512
useServerPreparedStmts=false

Cabe mencionar que estos parámetros funcionan sólo si en la aplicación se utilizan PreparedStatements como método de ejecución de queries a la base de datos.

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar: Resource > JDBC > Connection pools
• Click en <poolName>.
• Click en tab Additional Properties.
• Click en Add Properties.
• Agregar propiedades en forma Name=Value.
• Click en Save del lado derecho.
• Reiniciar el servidor.

10. Configuración del Garbage Collector

En términos generales, el Garbage Collector es una de las piezas de la JVM que más cambios ha sufrido desde que Java hizo su aparición. Dicho componente permite recuperar memoria una vez un objeto ha sido descartado; sin embargo el demonio del Garbage Collector requiere de cierto tuning pues en ambientes productivos puede saturarse, "deteniendo el mundo" constantemente mientras ejecuta la depuración de objetos. Libros enteros y miles de artículos han sido escritos para realizar este tipo de optimización, pero un par de pequeños cambios pueden ser más que suficientes.

Por default, se usa el modo "serial" de recolección, pero esto sólo sirve en máquinas con un solo CPU. Para servidores de aplicaciones con 2 o más cores, es recomendable seleccionar el modo "paralelo" mediante la opción -XX:+UseParallelGC. Por otro lado, éste se puede utilizar en modalidad de "pequeños impulsos frecuentes" (Concurrent Mark Sweep – CMS) mediante la opción -XX:+UseConcMarkSweepGC. Esta bandera disminuye un poco el rendimiento del sistema a cambio de no congelar la aplicación cada que se llena el GC.

• Ingresar a http:<hostname>:4848
• Firmarse con usuario/password administrador.
• Click en nodo de application server a editar > JVM Settings > JVM Options.
• Editar las opciones de la JVM o agregar nuevas en el campo de texto correspondiente: -XX:+UseParallelGC o en su caso, -XX:+UseConcMarkSweepGC
• Click en Save del lado derecho.
• Reiniciar el servidor.

h1

Por que un WAR no deberia costar US$25,000

03/13/2009

Wizdoc [Icon By Buuf]  Tips & Tricks.
La mayoría de las aplicaciones Java/JEE que se despliegan en un application server vienen en dos sabores:

• Unas, llamadas aplicaciones web, están compuestas principalmente por servlets, JSPs y/o algún framework que las integra. Éstas se encuentran empaquetadas en un archivo con terminación .WAR (Web ARchive) y en su mayoría utilizan alguna conexión a base de datos mediante pools de conexiones JDBC proporcionados por el application server. Las más elaboradas pueden llegar a utilizar servicios especializados, como colas de mensajes (JMS) para publicar web services asíncronos.

• Las otras, denominadas "aplicaciones empresariales" implementan todo lo anterior más tecnologías exóticas como Enterprise Java Beans (EJB) o conectores basados en el estándar JCA con sistemas externos (por ejemplo Jolt, de BEA). Dichas aplicaciones se empaquetan como archivos con terminación .EAR (Enterprise ARchive).

El problema

Mi cliente posee aproximadamente 200 aplicaciones en Java/JEE que pueden desglosarse de la siguiente manera:

• El 95% son aplicaciones que requieren un pool de conexiones JDBC; el restante 5% se conecta de manera directa (algo mal hecho, pero la mayoría de estas aplicaciones son viejas o están por ser reemplazadas por una versión más "estándar").

• El 20% son aplicaciones empresariales (EAR) que utilizan alguna combinación de EJBs, JMS o conectores JCA. El otro 80% son aplicaciones web (WAR).

• De ese 80% de aplicaciones web, cerca del 20% (un 16% del total) requieren algún servicio especializado como JMS o JCA. Las demás son aplicaciones "planas".

Este desgloce nos permite diferenciar las aplicaciones en dos grupos: aquellas que necesitan servicios básicos de JDBC (64%) y aquellas que necesitan servicios más sofisticados como JMS y EJBs (36%).

El problema se da cuando TODAS estas aplicaciones están montadas sobre un application server comercial (en este caso, Weblogic) pues si consideramos los siguientes precios de licenciamiento y soporte:

• Costo de una licencia de Weblogic por CPU: 0.75 x US$25,000 = US$18,750

• Costo del soporte y de una licencia de Weblogic por un año: 0.20 x US$18,750 = US$3,750

Y eso lo multiplicamos por el número total de procesadores donde corren las aplicaciones (unos 268 CPUs en alrededor de 50 servidores):

• Costo de licenciamiento = 268 CPUs x US$18,750 = US$5,025,000

• Costo anual del soporte = 268 CPUs x US$3,750 = US$1,005,000

Esto significa que por cada WAR o EAR desplegado en un application server, estamos quemándonos poco más de US$25,000 en licencias y unos US$5,000 en su soporte anual.

Sin embargo, esto no tiene por qué ser así.

Servlet containers y EJB containers

Así como los diferentes tipos de aplicaciones Java/JEE, existen diferentes tipos de servidores de aplicaciones. Unos, denominados servlet containers, permiten desplegar aplicaciones que contienen servlets y JSPs; algunos más especializados ofrecen servicios adicionales como JDBC, correo y JMS. Es decir, estos servidores sólo permiten desplegar WARs y los ejemplos más conocidos de su tipo son Tomcat, Resin o el Sun Web Server.

Por otro lado, existen los EJB containers o servidores "empresariales" que permiten desplegar aplicaciones muy robustas que implementan EJBs, JCA adicionalmente a los JSPs, servlets, JDBC y JMS; estos servidores son generalmente utilizados para desplegar aplicaciones EAR y los principales proponentes de esta tecnología son Websphere, Weblogic, JBoss y Glassfish.

Entonces, mi cliente está tirando dinero a la basura si para desplegar un WAR debe comprar una licencia a BEA/Oracle; si el problema es utilizar un pool de conexiones a JDBC (el principal pretexto para usar Weblogic) puede utilizar un servlet container que soporte dicho servicio: el Web Server de Sun permite este tipo de funcionalidad; con un poco de ingenio es posible implementar el mismo servicio en Tomcat o Resin.

Con esta información, podemos presentar un comparativo entre lo que cuestan las licencias y su soporte anual contra el costo de una pequeña migración de los 128 WARs que sólo necesitan JDBC:

• Costo por migrar un WAR de Weblogic a un servlet container estándar: US$1,000 (un estimado exorbitante, pero síganme la corriente)

• Costo por el soporte de un servlet container: US$450 anuales (esto es por CPU).

Así entonces:

• Costo de migración = 128 aplicaciones x US$1,000 = US$128,000

• Número de CPUs = CPUs usados por las 128 aplicaciones que corresponden al 64% del total = 0.64 x 268 CPUs = 172 CPUs

Entonces, el costo anual del soporte para los WARs migrados sería de alrededor de 172 x US$450 = US$77,400; y modificando los valores de licenciamiento y soporte de las aplicaciones que no se pudieron migrar:

• 72 aplicaciones ocupando 96 CPUs = US$1,800,000 en licencias y US$360,000 en soporte

Gran total = US$360,000 (Soporte Weblogic) + US$128,000 (la migración) + US$80,000 (soporte WARs redondeado) = US$568,000

Entonces, el cliente se está ahorrando US$437,000 tan solo el primer año, y en los años sucesivos se estará ahorrando US$565,000 por cada año (es decir, ya no habrá costos de migración). Adicionalmente, cada licencia para desplegar un EAR o WAR ahora costará en promedio US$9,000 y su soporte unos US$2,175

Gráficas comparando los costos de soporte durante 3 años. Al final de estos 3 años, de continuar con el esquema actual, el cliente habría gastado poco más de 4 millones de dólares en soporte. Usando una especialización de servidores, es posible ahorrar US$2,132,000 (poco más del 53% del costo original). (Click en imagen para ver en mayor tamaño)

Conclusiones

Para desplegar WARs no es necesario adquirir un costoso application server pues con un servlet container puede bastar; dejemos las soluciones propietarias sólo para casos que realmente lo requieran* y si podemos generar un análisis de Costo Total de Propiedad (Total Cost of Ownership – TCO) demostrando los beneficios al optimizar recursos, podremos vender una migración al cliente ahorrándole casi el 50% de sus costos operativos y dándonos de paso una buena ganancia.

[*]. La adopción de opensource puede ser un tema bastante espinozo porque a muchos clientes les da frío debido a que "no tiene soporte". Sin embargo, si podemos garantizar el soporte y mantenimiento de este tipo de productos (como parte de unos servicios profesionales), podremos echarnos al bolso hasta al cliente más anti-opensource.

h1

Permisos y Politicas de Seguridad con Java Frameworks: el caso de Glassfish

06/25/2008

Wizdoc [Icon By Buuf]

 Tips & Tricks

Como parte de las mejores prácticas del deployment de aplicaciones JEE, siempre es recomendable incluir en los WARs o EARs de nuestra distribución sólo el código que nosotros estamos implementando. Es decir, los frameworks y librerías de terceros que utiliza la aplicación deben estar fuera del directorio WEB-INF/lib, dejando en éste sólo los JARs que contengan nuestros componentes. Se ofrece un ejemplo utilizando esta distribución:

${j2ee-modules}
+—(Otras instalaciones)
.
.
+—MiAplicacion (directorio explotado / .WAR)
    |  web.xml
    |  struts-config.xml
    |
    +—web
    |      index.jsp
    |      error.jsp
    |
    —WEB-INF
        +—lib
        |      miscomponentes1.jar
        |      miscomponentes2.jar
        |
        —classes
               servlet1.class

En este post veremos cómo podemos extraer esos JARs de nuestras distribuciones para realizar un despliegue más limpio de la aplicación mientras al mismo tiempo mejoramos la seguridad sobre el código que estamos ejecutando; como ejemplo de configuración del servidor de aplicaciones utilizaremos el Sun Glassfish Enterprise Server (antes llamado Sun Java System Application Server), aunque este tipo de solución no se limita a éste.

Glassfish: aspectos de seguridad

Por defecto, la seguridad del Glassfish se encuentra activa. Es decir, a diferencia de otros servidores de aplicaciones como Weblogic, la creación y configuración de un dominio out-of-the-box contiene habilitadas todas las restricciones de seguridad, permitiendo sólo tareas como manipulación de sockets y archivos y generando errores de seguridad del género XPermissionException durante el despliegue o ejecución de "código no permitido". Aqui se muestran los permisos manejados por Java y aqui cómo deben manejarse estos privilegios en el código aplicativo.

Por otro lado, la mayoría de los frameworks actualmente disponibles – ya sea de web services como Axis2, persistencia como Hibernate o web como Java Server Faces – requieren ciertos permisos, de los cuales probablemente los más requeridos sean la creación y recuperación del Java ClassLoader y generar instancias de objetos a través de Reflexión.

Existen dos maneras de facilitar la ejecución de código que requiera permisos especiales de seguridad. La primera es fácil de implementar, aunque no es segura pues implica deshabilitar la seguridad de ejecución de código y la otra es un poco más complicada pero acota los permisos sólo al código o librerías que los requieren.

Deshabilitando la seguridad: la manera fácil

Para deshabilitar la seguridad durante la ejecución de código basta con ingresar a la consola de administración del Glassfish y editar las opciones de tiempo de ejecución de la Máquina Virtual de Java [Arbol de tareas >> Configuraciones >> <server-config> >> Configuración JVM >> Pestaña Opciones JVM]. En la pantalla mostrada eliminamos la opción -Djava.security.policy que indica la localización del archivo donde se encuentran configuradas las políticas de seguridad, como se muestra a continuación:


Eliminación de la variable -Djava.security.policy en la consola de administración del Glassfish. Nota: la pantalla muestra la solución para la Glassfish 8.2, pero también aplica para las siguientes, incluyendo la 9.1 U2.

La alternativa no gráfica a la solución consiste en eliminar o comentar la línea correspondiente en el archivo de configuración del dominio, encontrado en el archivo domain.xml y localizado en el directorio ${dir_install}/domains/<domain>/config/

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE domain … >

<domain … >
 <applications>…</applications>
 <resources>…</resources>
 <configs>
  <config dynamic-reconfiguration-enabled="true" name="server-config">
   <http-service>…</http-service>
   <java-config classpath-suffix="…">
    <!– various required jvm-options –>
    <jvm-options>…</jvm-options>
    <jvm-options>
     -Djava.security.policy=
${com.sun.aas.instanceRoot}/config/server.policy

    </jvm-options>
   </java-config>
    …
  </config>
 </configs>
</domain>

Sin embargo es conveniente observar que esta solución sólo es recomendable para ambientes de desarrollo, pues efectivamente estamos deshabilitando la seguridad de la JVM sobre la carga y ejecución de nuestras clases. Si en la aplicación existe código malicioso, podemos encontrarnos con una falla de seguridad potencialmente peligrosa.

Deshabilitando la seguridad: la forma talachuda pero correcta

Con esta opción, podremos designar en el archivo de políticas de seguridad sólo los permisos que nuestro código requiere. Dichas políticas se encuentran en el bloque grant del archivo server.policy localizado en el directorio de configuración del dominio ${dir_install} /domains/<domain>/config/

// Basic set of required permissions granted to all remaining code
grant {
  permission java.net.SocketPermission  "*", "connect";
  permission java.io.FilePermission   "<<ALL FILES>>", "read,write";
  permission java.util.PropertyPermission "*", "read";
};

Por otro lado, debemos depositar los JARs correspondientes a los frameworks y librerías externas en un directorio por separado. Por ejemplo, el directorio lib del dominio (${dir_install}/domains/domain1/lib/) es una buena opción si estamos compartiendo frameworks entre varios contextos aplicativos del dominio. Más tarde deberemos configurar el archivo de políticas de seguridad, agregando un bloque de permisos como se muestra a continuación:

grant codeBase "file:/opt/glass91/domains/domain1/lib/-" {
  permission java.lang.RuntimePermission "createClassLoader";
  permission java.lang.RuntimePermission "getClassLoader";
};

Nótese que estamos permitiendo la carga o creación de un ClassLoader a todas las librerías y código encontrados en /opt/glass91/domains/domain1/lib/. La asignación de permisos puede ser tan general o tan puntual como sea requerido, dependiendo de la criticidad del permiso (aqui se describe en detalle el formato de los bloques grant en un archivo de políticas de seguridad).

Finalmente, puede presentarse un problema de errores ClassNotFoundException lanzados durante la carga o ejecución de la aplicación. Esto se debe a que Glassfish no carga las librerías o código de ningún otro lugar que no sea el WEB-INF/lib de la aplicación o el directorio de librerías del servidor (${dir_install}/lib). Como solución a este detalle debemos incluir en el classpath del servidor las librerías correspondientes. Esto se logra en la consola de administración mediante la ruta [Arbol de tareas >> Configuraciones >> <server-config> >> Configuración JVM >> Pestaña "configuración de ruta" o classpath]. Alternativamente podemos editar el archivo domain.xml e incluir las librerías de forma manual en la propiedad classpath-suffix de la etiqueta java-config.

Conclusión

La reusabilidad no se limita sólo a nuestros componentes sino también a las librerías y frameworks que usamos entre diferentes aplicaciones y contextos. Por ello, con esta sencilla solución tendremos WARs y EARs con mayor limpieza y consistencia de código, aplicando security checks sólo a los componentes que lo necesitan y obteniendo deployments más ligeros que se desplegarán con mayor velocidad.

h1

JSF with JSP 2.1 on Sun App Server 9.1

06/24/2008

Wizdoc [Icon By Buuf]

 Tips & Tricks

SP

Versión en Español

Otra vez la burra al trigo[1]. Now, a user needs to publish a web service implemented in XFire and two Web applications that include Java Server Faces, Spring and Hibernate on an application server; As we were originally asked for Weblogic licenses, we said to ourselves "ummm… we should include a Sun Application Server quotation instead, to see if they like it". Of course, considering they needed 10 licenses, the amount saved by our customers is pretty step (more than US$100,000 in savings) and we were asked for a small proof of concept (POC) that included load testing to see if the App Server was well suited to their needs.

As I had to do some juggling bringing together people from development, application support and infrastructure, I could not perform the tests as I wanted and I opted instead for the installation of two working instances of the last versions of the Application Server (8.2 and 9.1), in such way that they will eventually perform their own tests. I hope that by speaking with the department manager it is easier to close the deal, and I no longer have to be dealing with these boys. This is because every minute they keep annoying me with comments like "hey, I can do this with Weblogic, you can’t do it with your application server, do you?" and obviously I have to show them you can – as a reference, the Sun Application Server 9.1 is the equivalent in almost all functionality aspects to the Weblogic 9 – but the explanations consume a lot of my time and we can’t move forward with the installation and testing.

The technicalities

When deploying the web application on the Sun Application Server 9.1 it throws the following error:

[#| 2008-03-11T19:11:19.343+0000 | SEVERE | sun-appserver9.1 | javax.enterprise.system.container.web | _ThreadID=21;_ThreadName=httpSSLWorkerThread-8080-0;_RequestID=5b0615d8-92de-49d1-b74a-c07777fc1175; | StandardWrapperValve[jsp]: PWC1406: Servlet.service() para el servlet jsp desencadenó una excepción

org.apache.jasper.JasperException: /WEB-INF/tags/ext/body.tag(104,4) PWC6038: "${empty(extLocation)?"js/ext-2.0":extLocation}" contiene expresiones no válidas: javax.el.ELException: Error Parsing: ${empty(extLocation)?"js/ext-2.0":extLocation}

at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:62)
at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:357)
at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:169)

Where the code snippet that is the root of the problem is as follows:


 98 <%@ include file="inc/taglibs.jsp" %>
 99 <%@ tag import="org.apache.commons.beanutils.BeanUtils, java.util.Scanner"
100   dynamic-attributes="dynamicAttributes"
101   description="" %>
102
103 <c:set var="extLocation">
104   ${empty(extLocation)?"js/ext-2.0":extLocation}
105 </c:set>
106
107 <link rel="stylesheet" type="text/css" href="${extLocation} /resources/css/ext-all.css" />

And although at first glance there is nothing wrong with it and in some forums it is recommended to configure the web.xml to prevent expression parsing among the jsp tags, the real source of the problem has to do with the JSP specification, which is the version 2.1 for the Sun Application Server 9.1; the libraries that throw this kind of errors are written with the JSP 2.0 Specification.

The solution

The amazing news is, the new JSP Specification for JEE 5 requires spaces for the ternary operator (<expression> ? <if true> : <if false>). Therefore, if we change the previous code snippet with the following:


 98 <%@ include file="inc/taglibs.jsp" %>
 99 <%@ tag import="org.apache.commons.beanutils.BeanUtils, java.util.Scanner"
100   dynamic-attributes="dynamicAttributes"
101   description="" %>
102
103 <c:set var="extLocation">
104   ${empty(extLocation) ? "js/ext-2.0" : extLocation}
105 </c:set>
106
107 <link rel="stylesheet" type="text/css" href="${extLocation} /resources/css/ext-all.css" />

The application should work correctly. Note: You may need to do the same "space adding" scheme in more than one configuration file in order to prevent any error of this kind from happening again.

Conclusions

Although I do not know the JEE 5 Specification cin all its extent, it is unusual that it has problems with the OGNL libraries such as this, which are practically the base of any web framework like Struts or Tapestry. In the meantime, I am waiting for these "kids" to end up their load tests successfully so I can go with the Head Honcho to tell him "Did you see? Everything works just as fine; please sign this cheque here."

h1

JSF con JSP 2.1 sobre Sun App Server 9.1

03/11/2008

Wizdoc [Icon By Buuf]

 Tips & Tricks

EN

English Version

Otra vez la burra al trigo. En esta ocasión, un usuario necesita publicar un web service en XFire y dos aplicaciones web que incluyen Java Server Faces, Spring y Hibernate sobre un application server; Como originalmente nos pidieron licencias de Weblogic, dijimos "mmm…¿por qué no también les hacemos una cotización con el Sun Application Server, a ver si les gusta?" Por supuesto que considerando que son 10 licencias, el ahorro de nuestros usuarios es considerable (más de US$100,000) y nos pidieron hacer una pequeña prueba de concepto que incluyera carga para ver si dicho App Server les conviene.

Como tuve que hacer algunos malabares juntando a gente de desarrollo, soporte aplicativo e infraestructura, no pude realizar las pruebas como quería y mejor les dejé las dos últimas versiones del Application Server (8.2 y 9.1) instaladas y funcionando y ya ellos harán sus pruebas. Espero que hablando ya con el director del área me sea más fácil cerrar el business y no tener que estar lidiando con estos chavos. Esto porque a cada ratito me sacan la de "oye, esto sí lo puedo hacer con Weblogic, ¿a poco no se puede con el tuyo?" y obvio que tengo que comprobarles que sí se puede – como referencia, el Sun Application Server 9.1 es equivalente en prácticamente todos los aspectos al Weblogic 9 – pero consume mucho de mi tiempo y no podemos avanzar con la instalación y pruebas.

Lo técnico

Al deployar la aplicación web en el Sun Application Server 9.1 aparece el siguiente error:

[#| 2008-03-11T19:11:19.343+0000 | SEVERE | sun-appserver9.1 | javax.enterprise.system.container.web | _ThreadID=21;_ThreadName=httpSSLWorkerThread-8080-0;_RequestID=5b0615d8-92de-49d1-b74a-c07777fc1175; | StandardWrapperValve[jsp]: PWC1406: Servlet.service() para el servlet jsp desencadenó una excepción

org.apache.jasper.JasperException: /WEB-INF/tags/ext/body.tag(104,4) PWC6038: "${empty(extLocation)?"js/ext-2.0":extLocation}" contiene expresiones no válidas: javax.el.ELException: Error Parsing: ${empty(extLocation)?"js/ext-2.0":extLocation}

at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:62)
at org.apache.jasper.compiler.ErrorDispatcher.dispatch(ErrorDispatcher.java:357)
at org.apache.jasper.compiler.ErrorDispatcher.jspError(ErrorDispatcher.java:169)

Donde el fragmento de código que genera el problema es el siguiente:


 98 <%@ include file="inc/taglibs.jsp" %>
 99 <%@ tag import="org.apache.commons.beanutils.BeanUtils, java.util.Scanner"
100   dynamic-attributes="dynamicAttributes"
101   description="" %>
102
103 <c:set var="extLocation">
104   ${empty(extLocation)?"js/ext-2.0":extLocation}
105 </c:set>
106
107 <link rel="stylesheet" type="text/css" href="${extLocation} /resources/css/ext-all.css" />

Y aunque a primera instancia no se ve nada extraño y en algunos foros recomiendan configurar el web.xml para que no parsee las expresiones de los jsp tags, en realidad el problema tiene que ver con la especificación JSP, que para el Sun Application Server 9.1 es la versión 2.1 y las librerías que marcan este tipo de errores están compiladas con la especificación JSP 2.0.

La solución

Lo más sorprendente es que la nueva especificación para J2EE 5 requiere espacios para el operador trinario (<expresión> ? <if true> : <if false>). Por lo que si cambiamos el código previamente mostrado por lo siguiente:


 98 <%@ include file="inc/taglibs.jsp" %>
 99 <%@ tag import="org.apache.commons.beanutils.BeanUtils, java.util.Scanner"
100   dynamic-attributes="dynamicAttributes"
101   description="" %>
102
103 <c:set var="extLocation">
104   ${empty(extLocation) ? "js/ext-2.0" : extLocation}
105 </c:set>
106
107 <link rel="stylesheet" type="text/css" href="${extLocation} /resources/css/ext-all.css" />

La aplicación deberá funcionar correctamente. Nota: Es posible que sea necesario hacer esta misma "adición de espacios" en más de un archivo de configuración para que se elimine cualquier error de este tipo.

Conclusiones

Aunque no tengo bien al 100% la especificación J2EE 5, me causa extrañeza que tenga problemas con las librerías OGNL como ésta, que son prácticamente la base de cualquier framework web como Struts o Tapestry. Por lo pronto, a ver que las pruebas de estos "niños" terminen satisfactoriamente para que pueda llegar con el patrón a decirle "¿Vio? todo funciona muy bien; firme el cheque aquí por favor."

h1

Java Server Faces sobre Sun Application Server: ¿Vale la pena?

06/29/2007

Wizdoc [Icon By Buuf]  Tips & Tricks
Java Server Faces: El último grito de la moda

Continuando con mis labores de Software Architect/SQA/Application Server Administrator (o simplemente, maistro albañil como decimos por aquí), me ha tocado la nada fácil tarea de hacer funcionar una implementación de Java Server Faces sobre el Sun Application Server 8.2. Para los neófitos en el asunto, JSF (JSR 252 para la versión 1.2 de dicha especificación) básicamente define cómo construir un framework MVC basado en eventos y componentes parecido a Tapestry, al contrario de frameworks basados en operaciones como es el caso de Struts.

El conflicto

Pues bien, ésta muy particular implementación de JSF incluye las librerías de Oracle (Oracle ADF Faces) así como de Apache (Apache MyFaces Project). El porqué de incluir ambos frameworks en una misma aplicación es todo un misterio (ya nos hemos puesto algunos madrazos el dueño de la aplicación y yo, pero eso viene más adelante), y me quisieron aplicar la de "… pues sentimos que el Application Server de Sun no está dando el ancho, y recomendamos pasarnos a Weblogic o Tomcat porque ellos sí soportan nuestra aplicación…"

¿Que que que? ¿Pardone moi? ¿Darle entrada a la competencia sólo porque estos weyes no saben cómo deployar su aplicación en SJSAS 8.2? ¡De ninguna manera! Se las voltee: "…Dame tu WAR; les aseguro – al tipo este, mi sponsor y al dueño del proyecto por parte del cliente – que en dos días tengo la aplicación corriendo en el Application Server, y tengo plena confianza en que las broncas son porque has de tener algún batidillo ahí adentro."

Je je, creo que la regué, pues no sólo puse en juego el honor de mi empresa, sino el de mi persona; en caso de no lograr el famoso deploy posiblemente hubiéramos perdido en grande, pues el dueño de la aplicación es uno de los gerentes de desarrollo que nos han apoyado desde el principio y en caso de quedar mal con él, muy difícilmente nos hubiésemos recuperado.

La Victoria

Después de sudarle un buen, estar al borde de tirar la toalla e incluso no poder disfrutar plenamente de la grata sorpresa del México-Brasil de ayer, me apliqué un sombrerito. Hoy hice entrega de la aplicación corriendo, y mandé un correo diciéndole a estos cuates que "… además de no apegarse a la especificación J2EE, que ya de por sí es malo, la aplicación contiene errores de diseño e implementación que difícilmente un programador amateur pasaría por alto…". ¡Goooooooool! (Sobra decir que ya no tuve que lidiar con estos cuates; sólo mandé un correo indicando los detalles de la aplicación a mis clientes, quienes por cierto le metieron una chinga a aquéllos por decir burradas sin estar plenamente informados).

Un Huevo Duro - galerias.ojodigital.com
Hay dos cosas infinitas: el Universo y la estupidez humana. Y del Universo no estoy seguro — Albert Einstein

Qué tenía y cómo se solucionó

Pasando al aspecto técnico, la aplicación tenía varios detalles cuya resolución es difícil de encontrar pues son bugs poco documentados:

1. Excepción relacionada a los drivers de JDBC de Oracle 10g:

java.lang.SecurityException: Sealing violation exception

2. Excepción lanzada en tiempo de ejecución al acceder a la página principal de la aplicación y que tenía que ver con la implementación de JSF:

Exception in PhaseListener RESTORE_VIEW 1 afterPhase
java.lang.NullPointerException at …

3. Warnings asociados al Digester de Apache commons que no se veían bien (Tip: siempre que sale un null en el log, es bueno revisar si no hay algo mal):


WARN org.apache.commons.digester.Digester – [ConverterRule]{faces-config/converter} Merge(null,java.math.BigDecimal)
WARN org.apache.commons.digester.Digester – [ConverterRule]{faces-config/converter} Merge(null,java.math.BigInteger)

La solución de los tres está directamente relacionada a las librerías de la aplicación (cabe mencionar que el WAR que me entregaron pesaba 32 MB de JARs y sólo 400 KB de código efectivo).

Tip: Para frameworks y librerías que no se generan por compilación, es mejor idea dejarlas en el path del dominio de la aplicación (no en el WAR por aspectos de desempeño ni en el classpath del servidor por aspectos de reutilización del ambiente).

La solución se da casi en automático:

1. Los JARs de conexión a base de datos de Oracle 10g del application server chocaban con tres archivos de drivers que tenía la aplicación. Oracle recomienda que para este error, se elimine del classpath (de servidor, de dominio y dentro del WAR) cualquier otro driver de JDBC que no sea el correspondiente a Oracle 10g.

2. Los JARs con la implementación de Java Server Faces dentro de la aplicación (5 en total, incluyendo los de Oracle así como los de MyFaces) chocaban con la implementación ya incluida en el SJSAS 8.2. Por esto no tronaba en Tomcat 6 o en Weblogic 8: ambos servidores no soportan JSF out-of-the-box, por lo que el deployment funcionaba en ellos sin problemas. Para este caso particular, tuve que eliminar[1] las librerías con la implementación de JSF del Sun Application Server.

3. Los warnings desaparecieron al corregir el punto 2.

Punto extra: de 68 JARs que contenía la aplicación sólo se requerían 14; los demás se incluyeron porque supongo que formaban parte del template o proyecto base que utiliza el proveedor; sin embargo creo que se le olvidó al arquitecto borrar lo que no se necesitaba (un error muy común pero que puede tener repercusiones muy serias, como es este caso).

Excelente! Pero… ¿Valió la pena? (Comercialote para Apache Tapestry)

Finalmente, la aplicación que instalé tiene integrados Spring y Hibernate (aunque todavía en etapa de desarrollo temprano). El cómo se resolverán los detalles de integración… me imagino que será todo un show (pero seguramente tendré que volver a defender las bondades del SJSAS). Lo que sí es una realidad es que estos cuates necesitan un nuevo arquitecto porque están reinventando la rueda con algo que ya existe desde hace rato: Apache Tapestry.

Hace poco más de 5 años aprendí a usar el framework de desarrollo Tapestry y leyendo la especificación de JSF me pude dar cuenta que prácticamente estaban describiendo los inner workings de aquél. Tomando eso en consideración, ¿no sería más productivo instalar un Tapestry que un remedo mal parchado de una especificación que pocos conocen? Por experiencia, Tapestry siempre ha sido mi elección en cuanto a MVCs, porque además tiene integración con Spring y Hibernate, es fácil de aprender y es muy flexible. Yo creo que este framework será el heredero de Struts, pues ya está a medio camino entre un MVC convencional y una implementación madura de JSF. Para terminar, incluso existe en The Server Side una comparación entre JavaServer Faces vs. Tapestry, ¿y quién creen que gana? ;D).


Notas y Pies de página

1. Es necesario aclarar que de acuerdo a las licencias del Application Server, al hacer esto [quitar librerias del Sun Application Server para que un framework de terceros pueda funcionar] puede cancelarse el soporte hacia el producto.

h1

Como montar Spring en Sun Application Server

06/21/2007

Wizdoc [Icon By Buuf]

 Tips & Tricks

Como parte de mi función de arquitecto, he realizado la instalación de varias aplicaciones sobre el Sun Java System Application Server, Enterprise Edition. Las versiones del Application Server sobre las que he trabajado han variado desde la 8.0, 8.1 y 8.2, hasta el Glassfish (equivalente a la versión 9).

Asimismo, también se ha requerido de mi parte el verificar que estas aplicaciones se encuentren adheridas a la especificación JEE y que sigan mínimamente las recomendaciones sugeridas por los Sun enterprise patterns – por ejemplo, que los desarrolladores utilicen los pools de conexiones ofrecidos por el contenedor, o que exista una nomenclatura estándar entre los paquetes que componen sus aplicaciones.

Tomando esto en cuenta, el pasado martes se requirió que instalara una aplicación que incluye varios frameworks de desarrollo, incluyendo Tapestry, web services desarrollados en Axis y Spring. De todos, Spring ha sido el framework que más lata dió al pasarlo del ambiente de desarrollo del programador (Apache Tomcat 6.0.10) a su nuevo dominio dentro de los servidores de desarrollo del cliente (un servidor Sun Fire V240 con la versión 8.2 del Application Server), al grado que de primera instancia mucha gente considera que este servidor no soporta Spring. Ooops.

Sin embargo, armándose de paciencia y aplicándose una A+ en Google Search, existe una manera relativamente sencilla de montar Spring sobre el SJSAS 8.2:

1. Instalación de módulo web (.war) en instancia del application server:


Instalación del aplicativo en el SJSAS 8.2

2. La instalación marcó un error al subir la aplicación:

[#|2007-06-19T11:23:43.547-0500|SEVERE|sun-appserver-ee8.2|org.springframework.web.context.ContextLoader|_ThreadID=15;|Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘locator’ defined in ServletContext resource [/WEB-INF/app/core/conf/core-cfg.xml]: Cannot resolve reference to bean ‘accessControllerCfg’ while setting bean property ‘modules’ with key [accessController]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘accessControllerCfg’ defined in ServletContext resource [/WEB-INF/app/core/conf/core-cfg.xml]: Cannot resolve reference to bean ‘accessController’ while setting bean property ‘module’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘accessController’ defined in ServletContext resource [/WEB-INF/app/core/conf/core-cfg.xml]: Instantiation of bean failed; nested exception is java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘accessControllerCfg’ defined in ServletContext resource [/WEB-INF/app/core/conf/core-cfg.xml]: Cannot resolve reference to bean ‘accessController’ while setting bean property ‘module’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘accessController’ defined in ServletContext resource [/WEB-INF/app/core/conf/core-cfg.xml]: Instantiation of bean failed; nested exception is java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘accessController’ defined in ServletContext resource [/WEB-INF/app/core/conf/core-cfg.xml]: Instantiation of bean failed; nested exception is java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)
java.security.AccessControlException: access denied (java.lang.reflect.ReflectPermission suppressAccessChecks)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
    at java.security.AccessController.checkPermission(AccessController.java:427)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
    at java.lang.reflect.AccessibleObject.setAccessible(AccessibleObject.java:107)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:169)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:148)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:52)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:486)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:362)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:145)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:186)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:106)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1046)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:857)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:378)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:145)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:186)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:106)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveManagedMap(BeanDefinitionValueResolver.java:235)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:118)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1046)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:857)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:378)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:145)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:283)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:313)
    at org.springframework.web.context.support.AbstractRefreshableWebApplicationContext.refresh(AbstractRefreshableWebApplicationContext.java:139)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:252)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:190)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4010)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4521)
    at com.sun.enterprise.web.WebModule.start(WebModule.java:241)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:827)
    at org.apache.catalina.core.ContainerBase.access$000(ContainerBase.java:125)
    at org.apache.catalina.core.ContainerBase$PrivilegedAddChild.run(ContainerBase.java:147)
    at java.security.AccessController.doPrivileged(Native Method)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:809)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:646)
    at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1331)
    at com.sun.enterprise.web.HttpServiceWebContainer.loadWebModule(HttpServiceWebContainer.java:867)
    at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1040)
    at com.sun.enterprise.server.WebModuleDeployEventListener.moduleDeployed(WebModuleDeployEventListener.java:160)
    at com.sun.enterprise.server.WebModuleDeployEventListener.moduleRedeployed(WebModuleDeployEventListener.java:308)
    at com.sun.enterprise.admin.event.AdminEventMulticaster.invokeModuleDeployEventListener(AdminEventMulticaster.java:922)
    at com.sun.enterprise.admin.event.AdminEventMulticaster.handleModuleDeployEvent(AdminEventMulticaster.java:905)
    at com.sun.enterprise.admin.event.AdminEventMulticaster.processEvent(AdminEventMulticaster.java:427)
    at com.sun.enterprise.admin.event.AdminEventMulticaster.multicastEvent(AdminEventMulticaster.java:139)
    at com.sun.enterprise.ee.admin.mbeans.ServerRuntimeMBean.forwardEvent(ServerRuntimeMBean.java:64)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at com.sun.enterprise.admin.MBeanHelper.invokeOperationInBean(MBeanHelper.java:305)
    at com.sun.enterprise.admin.runtime.BaseRuntimeMBean.invoke(BaseRuntimeMBean.java:386)
    at com.sun.jmx.mbeanserver.DynamicMetaDataImpl.invoke(DynamicMetaDataImpl.java:213)
    at com.sun.jmx.mbeanserver.MetaDataImpl.invoke(MetaDataImpl.java:220)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:815)
    at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:784)
    at sun.reflect.GeneratedMethodAccessor21.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at com.sun.enterprise.admin.util.proxy.ProxyClass.invoke(ProxyClass.java:54)
    at $Proxy1.invoke(Unknown Source)
    at com.sun.enterprise.admin.server.core.jmx.SunoneInterceptor.invoke(SunoneInterceptor.java:272)
    at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1408)
    at javax.management.remote.rmi.RMIConnectionImpl.access$100(RMIConnectionImpl.java:81)
    at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1245)
    at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1341)
    at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:782)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:294)
    at sun.rmi.transport.Transport$1.run(Transport.java:153)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.rmi.transport.Transport.serviceCall(Transport.java:149)
    at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:466)
    at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:707)
    at java.lang.Thread.run(Thread.java:595)
|#]
3. Este error es relacionado al nivel de seguridad con el que cuenta la librería spring.jar – específicamente, a la falta de permisos para invocar métodos protegidos o privados mediante reflexión. Para corregirlo, es necesario incluir los permisos correspondientes (java.security.AllPermission) al archivo de políticas del servidor (server.policy). De aquí se desprenden dos situaciones:

3.1. La librería spring.jar se encuentra embebida dentro del directorio WEB-INF/lib del war; esto no es recomendable porque con relativa frecuencia, varias aplicaciones implementadas con el mismo framework residen en el mismo dominio; esto implica carga de las mismas clases una y otra vez mediante el classloader de Java y en algunos casos puede volverse un problema de desempeño. Por limpieza general, siempre es recomendable que los frameworks se encuentren instalados en el classpath de la aplicación, no embebida en ésta.

Nota: Tampoco es aconsejable dejar dichas librerías instaladas en el classpath del servidor, (${com.sun.aas.installRoot}/lib) porque a menos que TODAS las aplicaciones estén implementadas con los mismos frameworks y misma versión de los mismos, puede generarse un problema de incompatibilidad de versiones en tiempo de ejecución.

3.2. Al encontrarse la librería dentro del jar, para asignar los permisos necesarios, tendrían que ser asignados a TODO el war, lo que equivale a "apagar" la seguridad para la aplicación. Dicho riesgo de seguridad es inaceptable.

Por ambos motivos es necesario extraer la librería del war y dejarla en un directorio aparte.

4. El procedimiento de extracción/generación de permisos para spring.jar fue el siguiente:

4.1. Se eliminó spring.jar del directorio WEB-INF/lib dentro del war.

4.2. Dicha librería se depositó dentro del directorio lib en el dominio donde se ejecutará la aplicación:

${com.sun.aas.installRoot}/domains/
    ${administrative.domain.name}/lib/frameworks/
    spring/spring.jar

donde:
${com.sun.aas.installRoot} = ROOT de instalación del application server (por ejemplo /opt/SunAppServer).
${administrative.domain.name} = Nombre del dominio donde está corriendo la aplicación (por ejemplo, domain1).

4.3. Se agregó la librería al classpath del dominio, modificando el archivo domain.xml localizado en la siguiente ruta:

${com.sun.aas.installRoot}/domains/
    ${administrative.domain.name}/config

4.4. Se asignaron los permisos de seguridad al archivo server.policy localizado en la siguiente ruta:

${com.sun.aas.installRoot}/domains/
    ${administrative.domain.name}/config

Al agregar las siguientes líneas al final de dicho archivo:

//Permissions added to Spring classes
grant codeBase "file:${com.sun.aas.installRoot}/domains/
    ${administrative.domain.name}/lib/frameworks/
    spring/spring.jar" {
    permission java.security.AllPermission;
};

Mediante este procedimiento deberían correr la mayoría de las aplicaciones con Spring en cualquiera de las versiones del Sun Application Server.

Sin embargo, para este caso especial, se está utilizando la funcionalidad de JMX (Java Management Extensions) desde Spring. Esto provoca otro error en tiempo de despliegue:

[#|2007-06-19T14:43:00.266-0500|SEVERE|sun-appserver-ee8.2|org.springframework.web.context.ContextLoader|_ThreadID=10;|Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘mbeanExporter’ defined in ServletContext resource [/WEB-INF/app/core/conf/core-cfg.xml]: Initialization of bean failed; nested exception is java.security.AccessControlException: access denied (javax.management.MBeanTrustPermission register)
java.security.AccessControlException: access denied (javax.management.MBeanTrustPermission register)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:264)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:568)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.checkMBeanTrustPermission(DefaultMBeanServerInterceptor.java:1724)
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:335)
    at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:497)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:585)
    at com.sun.enterprise.admin.util.proxy.ProxyClass.invoke(ProxyClass.java:54)
    at $Proxy1.registerMBean(Unknown Source)
    at com.sun.enterprise.admin.server.core.jmx.SunoneInterceptor.registerMBean(SunoneInterceptor.java:249)
    at org.springframework.jmx.export.MBeanExporter.doRegister(MBeanExporter.java:566)
    at org.springframework.jmx.export.MBeanExporter.registerMBean(MBeanExporter.java:485)
    at org.springframework.jmx.export.MBeanExporter.registerBeanInstance(MBeanExporter.java:461)
    at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:441)
    at org.springframework.jmx.export.MBeanExporter.registerBeans(MBeanExporter.java:368)
    at org.springframework.jmx.export.MBeanExporter.afterPropertiesSet(MBeanExporter.java:312)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1091)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:396)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:233)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:145)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:283)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:313)
    at org.springframework.web.context.support.AbstractRefreshableWebApplicationContext.refresh(AbstractRefreshableWebApplicationContext.java:139)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:252)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:190)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:49)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4010)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:4521)
    at com.sun.enterprise.web.WebModule.start(WebModule.java:241)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1086)
    at org.apache.catalina.core.StandardHost.start(StandardHost.java:847)
    at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1086)
    at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:483)
    at org.apache.catalina.startup.Embedded.start(Embedded.java:894)
    at com.sun.enterprise.web.WebContainer.start(WebContainer.java:741)
    at com.sun.enterprise.web.HttpServiceWebContainer.startInstance(HttpServiceWebContainer.java:935)
    at com.sun.enterprise.web.HttpServiceWebContainerLifecycle.onStartup(HttpServiceWebContainerLifecycle.java:50)
    at com.sun.enterprise.server.ApplicationServer.onStartup(ApplicationServer.java:300)
    at com.sun.enterprise.server.PEMain.run(PEMain.java:294)
    at com.sun.enterprise.server.PEMain.main(PEMain.java:220)
|#]
Cuya resolución es muy parecida a la del error anterior:

Se asignaron los permisos de seguridad al archivo server.policy localizado en la siguiente ruta:

${com.sun.aas.installRoot}/domains/
    ${administrative.domain.name}/config

Incluyendo la línea correspondiente al registro de Managed Beans – MBeans a la declaración de permisos de la librería spring.jar:

//Permissions added to Spring classes
grant codeBase "file:${com.sun.aas.installRoot}/domains/
    ${administrative.domain.name}/lib/frameworks/
    spring/spring.jar" {
    permission java.security.AllPermission;
    permission javax.management.MBeanTrustPermission "register";
};

Finalmente, se incluye la configuración apropiada en el JRE para que exista la capacidad de asignar este permiso, incluyendo las siguiente líneas:

// JMX Management Extension added for spring framework
permission javax.management.MBeanTrustPermission "register";

En el archivo de políticas de seguridad de Java:

${com.sun.aas.javaRoot}/jre/lib/security/java.security

Donde ${com.sun.aas.javaRoot} = Directorio de instalación de Java (por ejemplo, como el application server ya trae una JVM, el directorio sería /opt/SunAppServer/JDK).

Conclusiones

Después de estas adecuaciones a la seguridad del entorno de Java y la refactorización de los componentes web de la aplicación, ésta levanta sin ningún problema y no se registran errores durante el tiempo de ejecución. El único punto que queda por mencionar con respecto al Sun Application Server es que es un tanto melindroso, y que para subir aplicaciones, éstas deben adherirse a la especificación JEE prácticamente al 100% para que puedan correr. Eso es bueno y malo, ya que por un lado detalles como el aspecto de la seguridad deben estar bien definidos e implementados; por otro, se limita un poco la difusión del SJSAS debido a que muchos desarrolladores prefieren un app server con funcionalidad DMFJ[1] instalada.


Notas y pies de página

1.DMFJ = Do My F*cking Job