svn switch –relocate svn://172.26.115.219:5555/svnrepo/EPardPolicyDetailInboud/trunk svn://localhost:5555/svnrepo/EPardPolicyDetailInboud/trunk
La mayoría de los sitios que se dedican a compartir audio alrededor del mundo, cuentan con miembros a los que, vaya sorpresa, les gusta el audio. Esto implica que participan en foros de audio, leen artículos técnicos e intentan respetar las reglas y procedimientos a los que otros han llegado en base a un consenso general.
Pero cuando uno llega a sitios de Argentina, se da cuenta que es un país “diferente”. Allí parece haber una predisposición genética para evitar reglas, por muy evidentemente beneficiosas que estas puedan ser, mantenerse estoicamente en la ignorancia sobre cualquier tema técnico y, especialmente, atacar a quien pretenda algo mejor.
En base a esos principios, se van generando mitos que van siendo adoptados como “verdades reveladas” por otros, sin vacilación.
Me propongo aquí pues, aclarar un poco el panorama.
Como primera medida, se va poniendo de moda el uso de formatos loseless para comprimir.
1 - Argumento: Yo ripeo en flac porque se escucha mejor…es una masa el flac, loco !!!
Realidad: FALSO
Todos los estudios que se han hecho hasta ahora demuestran que un mp3 o un ogg comprimido a mas de 192kb no es posible para el oído humano distinguirlo de un original.
La forma mas simple de dejar en evidencia al que dice semejante barbaridad es hacerle lo que se denomina “blind test”. Dicho test consiste en hacerle escuchar, a esa persona, música en formato “sin pérdida” y luego lo mismo en formato “con pérdida” sin que sepa que lo le estan haciendo escuchar y luego deberá decir cual era cual. Hay software para hacer el testeo mas dificil al vivo de turno, ya que va enviando pedazos de canción de forma aleatoria de ambas muestras. Ese tipo de test se llama ABX.
Esto, obviamente, no me lo invento yo, hay miles de charlas al respecto. Aquí hay una en uno de los foros mas importantes de audio Hydrogenaudio.
2 - Argumento: Yo me bajo cosas en flac porque le mejora el sonido.
Realidad: Falso
Flac es un formato sin pérdida. Eso quiere decir que, lo que comprimas con flac lo podrás volver a su estado original sin perder ni un solo bit, como winzip hace con los documentos.
Sin embargo, si tu original era una porquería, por ejemplo un mp3 monoaural a 56k, pues flac se escuchará tan porquería como el original. Flac NO modifica el sonido ni nada del original, simplemente lo comprime haciendo que pese menos.
3 - Argumento: Yo no bajo en flac porque, a veces, tiene pérdidas.
Realidad: Falso
Esta barbaridad la escuché varias veces en Btarg y es equivalente a decir que si guardo mi CV con winzip, algún día winzip me lo devolverá sin las letras A.
Un formato sin pérdida es sin pérdida. El resto es ignorancia pura.
4 - Argumento: Flac se usa como un mp3, pa’ escuchá. EAC, el log y esa gilada no sirven para nada.
Resp: Falso
¿ Si flac, como ya dijimos, no se escucha mejor que mp3, para que bajar flac ???
La ventaja de un formato loseless radica en poder tener el original, pero pesando casi la mitad.Teniendo el original, podré comprimir al formato que quiera todas las veces que quiera e incluso, quemarlo en un CD todas las veces que quiera.
¿ Como hacer para tener el original ?
La ÚNICA forma por el momento es EAC correctamente configurado en modo seguro. Existe un programa similar para Mac que es aceptado en la mayoría de los sitios dedicados al tema y existe también el dbPoweramp que aún no goza de la confianza de los sitios mas populares.
La forma de saber que uno tiene realmente una copia 1:1 del original y que fue bien hecha es tener el log de EAC.
Si no tengo el log no puedo estar seguro que lo que estoy bajando es una copia 1:1 del original y entonces, ¿ tiene sentido bajarse algo de 300mb cuando puedo bajarlo en mp3 por 60MB, con la misma calidad de sonido ? Esta clarísimo que no. Sin tener log y sin verificarlo, bajar flac es de subnormales.
Vamos ahora a ver la otra moda, la de los mp3.
Desde hace un tiempo podemos ver que se ha llenado de gente que sube cosas, con orgullo, ripeadas a 320kb en modo constante.
Veamos aquí algunas de las excusas:
1 - Argumento: yo quiero la máxima calidad por eso ripeo mp3 a 320kb constante (CBR).
Resp: Falso
A partir de 190kb, no hay diferencias perceptibles por el oído humano. Si querés hacer backup con toda la calidad, pues haces un ripeo en flac.
Sin embargo, la idea de los fomatos con pérdida es mantener una calidad aceptable ocupando poco espacio. Por lo tanto, una compresión de alrededor de 200kb te dará una calidad similar a los 320 pero ocupando menos. ¿ Qué te aportan los 320 constantes ? NADA
Y aún hay más.
2 - Argumento: comprimir mp3 en modo constante (CBR) es mejor, porque te da mas calidad de sonido.
Resp: FALSO
La música, para conservar la calidad, no necesita siempre la misma compresión. No es lo mismo comprimir el sonido constante y repetitivo de un tambor que una orquesta sinfónica interpretando a Beethoven. Por lo tanto, al usar el modo variable, le permitimos al compresor adaptarse a las circunstancias para ser mas eficientes.
Esto, obviamente, no me lo invento yo sino que lo dicen los creadores de LAME, por ejemplo en este thread de Hydrogenaudio.
Basicamente, esta gente dice:
Best quality: “archiving”
-b 320. This is the strongest setting for MP3, with the lowest risk of artifacts. With the exception of a few situations, quality is rarely better than the highest VBR profiles described below. However, ‘archiving’ music using a lossy format like MP3 is never recommended – no matter how transparent the resulting files might sound. The alternative is to use Lossless formats like WavPack, FLAC etc. that allow true archiving, bit for bit like on the original CD.
O sea, 320 es solo para achivar y siendo mp3 un formato con pérdida, usarlo para archivar es de empanados mentales. Lo lógico es usar un formato loseless.
High quality: HiFi, home or quiet listening
-V0(~245 kbps),-V1(~225 kbps),-V2(~190 kbps) or-V3(~175 kbps) are recommended. These settings will normally produce transparent encoding (transparent = most people can’t distinguish the MP3 from the original in an ABX blind test). Audible differences between these presets exist, but are rare.
Los settings a los que se refiere el texto de arriba, corresponden al uso por linea de comandos de LAME o, mucho mejor, a herramientas como LameDrop, un frontend similar al existente para Ogg Vorbis.
Ser ignorante no es malo por si mismo, nadie nace sabiendo. Pero cuando alguien hace una recomendación con fundamentos técnicos y que beneficia a todos y la gente no solo se niega a aprender sino que atacan y se burlan del que pide la mejora, estamos ante gente que, verdaderamente, da pena y verguenza ya que parecen ir con un cartel en el pecho que dice “ni se, ni me interesa aprender”.
Hacer las cosas bien cuesta prácticamente lo mismo que hacerlas mal. La decisión es totalmente personal.
En estos dias he tenido la necesidad de instalar una copia de Symfony en un host compartido. Yo ya había instalado dicho software en un par de máquinas pero de forma totalmente libre y standard, cosa que no ocurre en los hosting compartidos donde, normalmente, existe una estructura fija que no responde a las necesidad del framework.
En este caso, mi proyecto Symfony consistía en una estructura “de manual”:
myproject +---apps ¦ +---backend ¦ ¦ +---config ¦ ¦ +---i18n ¦ ¦ +---lib ¦ ¦ +---modules ¦ ¦ +---templates ¦ +---frontend ¦ +---config ¦ +---i18n ¦ +---lib ¦ +---modules ¦ +---templates +---batch +---cache ¦ +---frontend ¦ +---dev ¦ ¦ +---config ¦ ¦ +---i18n ¦ ¦ +---apps ¦ +---prod ¦ +---config ¦ +---i18n ¦ +---apps +---config +---data ¦ +---model ¦ +---sql +---doc +---lib ¦ +---model ¦ +---map ¦ +---om +---log +---plugins +---test ¦ +---bootstrap ¦ +---functional ¦ ¦ +---backend ¦ ¦ +---frontend ¦ +---unit +---web +---css +---images +---js +---uploads +---assets +---thumbnail
Al acceder al hosting me encuentro con una estructura donde:
El primer paso fue tomar conciencia de que era imposible instalar mi aplicación de esa forma. Symfony tiene un directorio web al que debe apuntar el virtual host y todo el resto de la aplicación debe estar por detrás de este.
En la empresa de hosting (servergrove) fueron muy gentiles, comprendieron el problema y me habilitaron un directorio por debajo de httpdocs para instalar mi aplicación. De esa forma, solo debería hacer que Symfony entendiera que su directorio web, normalmente dentro del árbol de la aplicación, estaba fuera de este, en httpdocs, algo que era posible por configuración.
Ahora el problema pasaba a ser la instalación de Symfony.
Primero intenté instalarlo a mano, descargando un tarball y poniéndolo en un directorio del host. Luego de muchas pruebas me rendí ya que no logré que eso funcionara.
La instalación normal y recomendada es utilizando PEAR y, dado que el hosting tenía PEAR instalado, imaginé que no vendría mal que hicieran la instalación ellos. Nuevamente la gente de servergrove me solucionó el problema y Symfony quedó instalado de forma standard en un directorio accesible por Apache y en el path.
Ahora ya podía ejecutar comandos de Symfony (por ejemplo limpiar el cache con symfony cc) dentro del directorio de mi aplicación. Y funcionaba !.
El siguiente paso consistía en indicarle a Symfony que el directorio web pasaría a ser httpdocs. Para hacer esto, es necesario ir al archivo index.php de nuestra aplicación y modificar lo siguiente:
define('SF_ROOT_DIR', dirname(__FILE__).'/../myproject');
De forma tal que SF_ROOT_DIR apunte a nuestra aplicación.
Luego, en el archivo config/config.php de nuestra aplicación hay que modificar dos paths:
sfConfig::add(array( 'sf_web_dir' => SF_ROOT_DIR.DIRECTORY_SEPARATOR.'www', 'sf_upload_dir' => SF_ROOT_DIR.DIRECTORY_SEPARATOR.'www'.DIRECTORY_SEPARATOR.sfConfig::get('sf_upload_dir_name'), ));
De forma tal que quede así:
sfConfig::add( array( 'sf_web_dir' => SF_ROOT_DIR.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'httpdocs', 'sf_upload_dir' => SF_ROOT_DIR.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'httpdocs'.DIRECTORY_SEPARATOR.sfConfig::get('sf_upload_dir_name'), ));
Cabe destacar que el archivo config a modificar es el de cada aplicación (por ej: myproject/apps/frontend/config.php) y no el del proyecto (myproject/config/config.php) porque el objeto sfConfig no esta disponible en el config del proyecto.
La instalación de Symfony tambien requiere establecer un alias en la definición del virtual host para algunos scripts y librerías que utiliza. Dado que esto es imposible en un hosting compartido, una solución sencilla es copiar el directorio sf de PEAR/symfony/ al directorio web de nuestra aplicación, en este caso httpdocs.
Con estos pasos, la aplicación esta lista para funcionar.
Normalmente, los DVD de fútbol son copias de alguna transmición, grabadas en formato DVD pero sin ningún tipo de protección (algo que no ocurre, por ejemplo, con las películas, las cuales deben ser desencriptadas con DVDDecrypt).
Si ese es el caso, hay que copiar el contenido de la carpeta video_ts del DVD, a una carpeta de nuestro disco duro. En este caso yo me hice una carpeta llamada tuto. La copia demora unos minutos, dependiendo de cuan rápida sea nuestra computadora y del tamaño de los archivos a transferir.
Una vez transferidos los archivos deberemos usar DGIndex, para comenzar el ripeo.
Paso 1 - Abrimos DGIndex y nos va a aparecer una ventana como la siguiente:
Vamos al menú File -> open
Y allí seleccionamos todos los archivos que acabamos de copiar.
y nos aparecerá una ventana con los archivos seleccionados
Una vez cargados los archivos, veremos en la pantalla principal la imagen de lo que estamos ripeando. Podemos verificar que la película/partido esta completo, deslizando la barra que aparece en la parte de abajo de la venta de DGIndex.
Cuando estemos seguros de que todo va bien, iremos al menú audio -> output method y verificaremos que este marcada la opción Demux all tracks.
Luego verificaremos que los settings de video esten igual que en la siguiente imagen:
Una vez verificado todo pasaremos a salvar nuestro proyecto como muestra la siguiente imagen:
El proyecto comenzará a guardarse mostrando una ventana similar a esta:
Cuando haya terminado, nos quedarán varios archivos. Por un lado las pistas de audio (.mp3, .mpa, .ac3, .wav) y el archivo de nuestro proyecto para hacer el rip (.d2v), como muestra la imagen de abajo.
Ya hemos terminado con DGIndex. Lo cerramos y ahora procesaremos el audio con BeLight.
Paso 2 - Abrimos BeLight y cargamos el archivo de audio que usaremos para el rip (el generado con DGIndex en los pasos anteriores). En la zona de la derecha veremos que hay varias solapas, elegiremos la última que nos permitirá transformar nuestro audio a wav, como muestra la imagen.
Luego hacemos click en Start Processing y esperamos a que termine (el proceso no suele ser muy largo.
Cerramos BeLight y ahora pasaremos a retocar nuestro video con GordianKnot.
Paso 3 - Abrimos GK y elegimos el archivo de nuestro proyecto d2v (el generado con DGIndex).
Eso nos abrirá una ventana que mostrará al video que pretendemos ripear como se ve en la siguiente figura:
Allí podremos ver que el video tiene areas que no contienen video (bandas negras) las cuales no necesitamos ripear. Lo que intentaremos es cortar esas partes (crop) para quitarlas de nuestro rip y al mismo tiempo adaptar el tamaño del video al corte que estamos haciendo.
Para eso nos iremos a la ventana principal y allí a la solapa llamada Resolution. A la derecha marcaremos Smart Crop All y hacemos click en Auto Crop tal cual muestra la siguiente figura.
Volvemos a la ventana que muestra el video y verificamos, moviendo la barra inferior para recorrer la imagen, que el crop haya sido correcto. Lo mejor en estos casos es buscar areas de video claras para poder apreciar si aún quedan bandas negras.
Si es así, podremos ajustar el corte usando los botones que estan a la derecha en la zona resaltada en la siguiente figura.
Hay cuatro áreas que representan las zonas de arriba, abajo, izquierda y derecha de la imagen. Valores mas altos implican mas corte de imagen; valores bajos, obviamente lo contrario.
Ahora haremos los ajustes finales de acuerdo a la siguiente imagen.
La idea es adaptar la imagen de la mejor forma al nuevo formato que ha adquirido luego de los cortes. Eso lo lograremos moviendo la barra que esta marcada con color rosa. A medida que la vamos moviendo, veremos que los valores de ancho y alto (width y height) de la zona roja van cambiando.
Debemos lograr que el tamaño quede en un formato aceptable (un ancho no menor a 320) con un error de aspecto (marcado en la zona verde) lo mas cercano a 0 y que los bits por pixel/frame no sean menores a 0.2 ni mayores a 0.3.
Cuando tenemos eso listo, hacemos click en la solapa bitrate y nos aseguraremos de que las opciones esten como se destaca en la siguiente imagen:
En la zona donde dice Total File Size, pondremos 700 MB si queremos que el rip entre en un CD. Eso nos dará un número en Average Bit Rate el cual debemos apuntar para usarlo luego.
Con todo listo vamos nuevamente a la ventana del video y hacemos click en File -> Save & Encode y luego verificamos de tener las opciones como se muestran aqui:
Finalmente haremos click en Save y cerraremos GordianKnot.
Ahora tenemos un archivo llamado .avs.
Paso 4 - Abrimos el fichero avs con el Notepad (Bloc de Notas) y veremos algo asi:
Luego de la línea que dice:
mpeg2source(”C:\TUTO\thewall.d2v”)
Debemos agregar estas dos líneas:
audio = WAVSource(”capture.wav”)
AudioDub(video, audio)
De forma tal de que el texto quede así:
Ahora vamos a nuestro explorador de windows y nos fijamos el nombre del archivo de audio (.wav) que hemos generado con BeLight. En mi caso el archivo se llama thewall T01 3_2ch 384Kbps DELAY 0ms.wav. Copiamos el nombre y luego vamos al Notepad y lo pegamos en la línea que dice WavSource y delante de la línea que dice mpeg2source ponemos video = , de forma tal de que quede así:
Ahiora ya podemos iniciar el proceso final con VirtualDub.
Paso 5 - Abrimos VirtualDub y vamos a File -> Open y buscamos el archivo .avs que acabamos de editar.
Su hemos hecho todo bien, se abrirá el video que estamos intentanto procesar en la ventana de VirtualDub.
Ahora debemos configurar Xvid, que es el codec que utilizaremos para comprimir. Usamos Xvid porque es compatible con DivX, es mejor que este y encima es freeware.
Vamos a Video -> Compression
Allí nos aparecerá una lista de codecs y elegiremos Xvid y luego haremos click en el botón configure de la derecha.
Primero nos dedicaremos a configurar VirtualDub, pero debemos recordar que esta paso hay que hacerlo solo una vez.
Tal cual deciamos arriba, al hacer click en el botón Configure se abre una ventana como esta:
Allí elegiremos en Profile Level el Advanced Simple L5 y luego haremos click en el botón More de esa misma línea donde nos aparecerá una nueva ventana que nos debe quedar igual a la siguiente:
Cuando la tenemos lista hacemos click en Ok lo que nos lleva a la ventana anterior.
Allí vamos a la parte inferior, y en Quality preset ponemos User Defined, para luego hacer click en el botón de la derecha More. Eso nos llevará a una ventana como esta:
Debemos dejar todas las opciones tal cual se ve en la imagen de arriba y luego hacer click en la solapa Quantization, lo cual nos llevará a una ventana como la siguiente:
donde nuevamente dejaremos los settings tal cual se ven en la imagen para luego hacer click en el botón Ok.
—————- continuará ——————————–
Jacarezinho, aviao, Jacarezinho, aviao
Cuidado com o disco voador
Tira essa escada daí,
Essa escada é pra ficar aqui fora
Eu vou chamar o síndico
Tim Maia! Tim Maia! Tim Maia! Tim Maia!
O trem corre no trilho da Central do Brasil
O trem corre no trilho da Central do Brasil
Incluindo “Paixao Antiga” e aquele beijo quente
Que eu ganhei da sua amiga
E o que que deu? Funk na cabeça.
e o que que deu? Funk na cabeça
Alo, alo, W/Brasil, alo, alo, W/Brasil
Jacarezinho, aviao, Jacarezinho, aviao
Cuidado com o disco voador
Tira essa escada daí,
Essa escada é pra ficar aqui fora
Eu vou chamar o síndico
Tim Maia! Tim Maia! Tim Maia! Tim Maia!
E o que que deu? Funk na cabeça
E o que que deu? Funk na cabeça
Deu no New York Times
Fernando, o belo, nao sabe se vai participar
Do próximo campeonato de surf ferroviário
Surfista de trem, surfista de trem
Deu no New York Times
A feira de Acari é um sucesso,
Tem de tudo, é um mistério
Deu no New York Times
Dizem que Cabral 1 descobriu a filial
Dizem que Cabral 2 tentou e se deu mal
Amor, dor, dor,
Lá da rampa mandaram avisar
Que todo dinheiro será devolvido
Quando setembro chegar
Num envelope azul índigo
Num envelope azul índigo
Chama o síndico!
Tim Maia! Tim Maia! Tim Maia! Tim Maia!
Alo, alo, W/Brasil, alo, alo, W/Brasil
Da central passando pela Mangueira
Dando uma volta na Pavuna e chegando em Madureira,
E lá que o samba rola de primeira.
E lá que o samba rola de primeira.
Alo, alo, tia Lea
Se tiver ventando muito nao venha de helicóptero
Alo, alo tia Lea
Se tiver ventando muito nao venha de helicóptero
Alo, alo, W/Brasil, alo, alo W/Brasil
Alo telefonista, me desperte i s 7h15,
Por favor Rádio Táxi 9h30 se nao o bicho pega,
Eu também quero graves, médios e agudos!
Eu vou chamar
Jacarezinho, aviao, Jacarezinho, aviao
Cuidado com o disco voador
Tira essa escada daí,
Essa escada é pra ficar aqui fora
Jorge Ben
Vengo trabajando desde hace bastante tiempo con Axis 1.x para hacer web services y desde hace poco vengo intentando adaptar mis proyectos a Maven.
Dentro de esa adaptación, uno de los temas pendientes era el poder generar el archivo wsdl o los stubs del cliente con Maven ya que varias veces lo intenté pero no pude hacer que funcionara y tampoco logré que alguien me diera una mano en la lista mailing list.
Mi servicio es algo muy simple y tenía una interface definida en com.bs.its.tp.cas.remoting.SingleSignonWS.
Esta vez seguí la instrucciones que estaban en la documentacion de axis tools agregando al pom de mi proyecto:
<plugin> <groupid>org.codehaus.mojo</groupid> <artifactid>axistools-maven-plugin</artifactid> <configuration> <filename>cas-ws.wsdl</filename> <location>http://dummy</location> <namespace>http://remoting.cas.tp.its.bs.com</namespace> <classesdirectory>${project.build.outputDirectory}</classesdirectory> <typemappingversion>1.2</typemappingversion> </configuration> <executions> <execution> <goals> <goal>java2wsdl</goal> </goals> </execution> </executions> </plugin>
Sin embargo, cuando intentaba ejecutar el “goal” me daba un error diciendo que no encontraba el class-of-port-type.
Probá un montón de cosas hasta que harto de todo decidí bajarme el código fuente del plugin.
Allá descubrí que el class-of-port-type era un parámetro. Y no era un parámetro cualquiera sino que era imprescindible (en las tareas de Axis no es opcional).
Volví a definir el plugin en el pom de la siguiente manera:
<plugin> <groupid>org.codehaus.mojo</groupid> <artifactid>axistools-maven-plugin</artifactid> <configuration> <classofporttype>com.bs.its.tp.cas.remoting.SingleSignonWS</classofporttype> <filename>cas-ws.wsdl</filename> <location>http://dummy</location> <namespace>http://remoting.cas.tp.its.bs.com</namespace> <classesdirectory>${project.build.outputDirectory}</classesdirectory> <typemappingversion>1.2</typemappingversion> </configuration> <executions> <execution> <goals> <goal>java2wsdl</goal> </goals> </execution> </executions> </plugin>
y ahora si funcionó.
07 Jul
Posted by admin as JDO/DAO, Java, Programación, Spring framework
Necesitaba mejorar una aplicación de web services que había realizado tiempo atrás por lo que me puse manos a la obra para bsucar las herramientas mas convenientes.
Decidí usar Hibernate para la capa DAO pero embebido en Spring de forma tal de que este manejara el tema transaccional y asi tambien aprovechar las ventajas que me podría aportar este framework (configuración con xml, IoC, etc).
Al comenzar a preparar todo me choqué con el mayor problema: como hacer para que se definiera el contexto de Spring al arrancar y que luego mi aplicación lograra acceder a dicho contexto para obtener las instancias de los objetos que Spring había creado.
Luego de muchas vueltas encontré una solución.
Spring cuenta con una clase llamada ServletEndpointSupport que sirve para estos fines.
Por lo tanto, hice mi aplicación asi:
Primero en mi web.xml definí lo siguiente:
<web -app> <display -name>Apache-Axis</display> </web> <param -name>contextConfigLocation</param> <param -value>/WEB-INF/applicationContext.xml, /WEB-INF/applicationContext-service.xml, /WEB-INF/applicationContext-dao.xml</param> <listener> </listener><listener -class> org.apache.axis.transport.http.AxisHTTPSessionListener </listener> <listener> </listener><listener -class> org.springframework.web.context.ContextLoaderListener </listener>
Donde con los contextparameter definí las propiedades que quería pasarle a mi aplicación al momento de arrancar para luego definir el contextloaderlistener de Spring, el cual se encargará de iniciar el contexto del framework.
Teniendo esto definido, mi aplicación puede hacer lo siguiente:
public class NotificationsWSImpl extends ServletEndpointSupport implements NotificationsWS { private NotificationService notificationServiceImpl; protected final void onInit() { this.notificationServiceImpl = (NotificationService) getWebApplicationContext().getBean("notificationService"); } }
de esta forma, mi aplicación puede obtener los beans definidos por Spring y asi acceder a la capa services/dao de una forma elegante.
En varias oportunidades he tenido que usar Quartz en mis aplicaciones, especialmente en los casos en que he necesitado que mis servicios web fueran asíncronos de forma tal que el cliente no se quedara esperando una respuesta.
Normalmente, Quartz viene con un servicio (un .sar) para JBoss. El servicio se integra bien y permite tener la configuración de Quartz aislada en un solo archivo dentro del sar.
Gracias a dicho servicio, dentro de nuestras aplicaciones podemos usar JBoss obteniendo el Scheduler guardado en JNDI.
Context context = new InitialContext(); sched = (Scheduler) context.lookup("Quartz"); jobDetail = new JobDetail("checkRemoteIsWorking_" + remoteBrokerName, // job name null, // job group (you can also specify 'null' to use the default group) CheckRemoteIsWorking.class); // the java class to execute jobDetail.getJobDataMap().put("properties", properties); SimpleTrigger trigger = new SimpleTrigger("trigger1" + startTime, // name "group1", // group new Date(startTime + (Long.parseLong(properties.getProperty("intervalo")) * 1000L)), // startTime null, // end Time SimpleTrigger.REPEAT_INDEFINITELY, // repeat infinitely (60L * 1000L) * Long.parseLong(properties.getProperty( "intervalo"))); // repeat Interval sched.scheduleJob(jobDetail, trigger); sched.start();
Si bien esto es muy útil, suele haber problemas.
Dado que Quartz fue instanciado dentro del contexto del sar, si tengo una aplicación en otro contexto (por ej: una webapp) y la clase implementadora de Job que Quartz debe ejecutar esta dentro de la webapp, Quartz no podrá ver dicha clase y, al momento de intentar ejecutarla, lanzará una excepción de “Class Not Found”.
En algunos casos, la opción pasa por poner a la clase que implementa job dentro del mismo contexto que Quartz o en el directorio lib de la instancia de JBoss donde estemos trabajando. Sin embargo, esa opción no solo no es muy elegante sino que tampoco es útil si nuestra clase depende de otras (por ejemplo, si nuestra clase utiliza metodos de iBatis o Hibernate) ya que deberíamos copiar tambien otras clases y archivos de configuración al directorio lib del servidor.
La solución elegante y práctica que he encontrado pasa por hacer una nueva instancia de Quartz dentro de la webapp. De esa forma, nuestros jars estan aislados dentro de su propio contenedor y podemos configurar Quartz de acuerdo a las necesidades particulares de nuestra aplicación.
Como primera medida, debemos crear un archivo llamado quartz.properties en algun lugar dentro de WEB-INF, por ejemplo: WEB-INF/classes/quartz.properties.
# Default Properties file for use by StdSchedulerFactory # to create a Quartz Scheduler Instance, if a different # properties file is not explicitly specified. # # org.quartz.scheduler.classLoadHelper.class = org.quartz.scheduler.instanceName = AxisQuartzScheduler org.quartz.scheduler.rmi.export = false org.quartz.scheduler.rmi.proxy = false org.quartz.scheduler.xaTransacted = false org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 5 org.quartz.threadPool.threadPriority = 4 org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate org.quartz.jobStore.dataSource = QUARTZ org.quartz.jobStore.nonManagedTXDataSource = QUARTZ_NO_TX org.quartz.jobStore.tablePrefix = QRTZ_ org.quartz.dataSource.QUARTZ.jndiURL = java:/QuartzDS org.quartz.dataSource.QUARTZ_NO_TX.jndiURL = java:/QuartzDS
En este caso, mi webapp es en realidad axis ya que mi aplicación es un web service dentro de axis. Como se puede ver, sigo utilizando el datasource de JBoss y defino mi instancia de Quartz con un nombre propio (AxisQuartzScheduler) lo cual me permitirá aislar e identificar problemas en las tareas lanzadas desde esta webapp.
Otra ventaja evidente es la de poder configurar el pool de threads para esta aplicación de forma indenpendiente y de acuerdo a necesidades particulares.
Ahora bien, nuestra webapp deberá instanciar Quartz ni bien arranque el servidor y para ello deberemos pasarle ciertos parámetros desde web.xml
<context -param></context> <param -name>config-file</param> <param -value>/classes/quartz.properties</param> <context -param></context> <param -name>shutdown-on-unload</param> <param -value>true</param> <context -param></context> <param -name>start-scheduler-on-load</param> <param -value>true</param>
Una vez hecho esto, definiremos un listener para que instancie Quartz al iniciarse el servidor:
<listener> </listener><listener -class>com.bs.proteo.notifications.service.NotificationServiceManager</listener>
Y dentro de nuestro listener haremos lo siguiente:
public class NotificationServiceManager implements ServletContextListener { private static Logger logger = Logger.getLogger(NotificationServiceManager.class); public static final String QUARTZ_FACTORY_KEY = "org.quartz.impl.StdSchedulerFactory.KEY"; private boolean performShutdown = true; private Scheduler scheduler = null; public void contextInitialized(ServletContextEvent sce) { if (logger.isInfoEnabled()) { logger.info("contextInitialized(ServletContextEvent) - start"); //$NON-NLS-1$ } ServletContext servletContext = sce.getServletContext(); StdSchedulerFactory factory; String prefixLogFile = sce.getServletContext().getRealPath("/WEB-INF"); try { String configFile = prefixLogFile + servletContext.getInitParameter("config-file"); String shutdownPref = servletContext.getInitParameter("shutdown-on-unload"); logger.info("config file: " + configFile); if (shutdownPref != null) { performShutdown = Boolean.valueOf(shutdownPref).booleanValue(); } // get Properties if (configFile != null) { logger.info("NotificationServiceManager - getting " + "quartz properties"); factory = new StdSchedulerFactory(configFile); } else { logger.info("NotificationServiceManager - No " + "quartz properties"); factory = new StdSchedulerFactory(); } // Should the Scheduler being started now or later String startOnLoad = servletContext.getInitParameter("start-scheduler-on-load"); scheduler = factory.getScheduler(); /** * If the "start-scheduler-on-load" init-parameter is not specified, * the scheduler will be started. This is to maintain backwards * compatability. */ if (startOnLoad == null || (Boolean.getBoolean(startOnLoad))) { // Start now scheduler.start(); logger.info("Scheduler has been started..."); } else { logger.info("Scheduler has not been started. Use scheduler.start()"); } logger.info("Storing the Quartz Scheduler Factory in the servlet context at key: " + QUARTZ_FACTORY_KEY); servletContext.setAttribute(QUARTZ_FACTORY_KEY, factory); } catch (Exception e) { logger.error("Quartz Scheduler failed to initialize: " + e.toString()); e.printStackTrace(); } try { AdminHelper.configure(sce.getServletContext().getRealPath("/WEB-INF/")); AdminHelper.setSf(scheduler); } catch (Exception e) { logger.error("contextInitialized(ServletContextEvent)", e); //$NON-NLS-1$ logger.error("Unable to configure MOM elements: " + e.getMessage()); e.printStackTrace(); } if (logger.isInfoEnabled()) { logger.info("contextInitialized(ServletContextEvent) - end"); //$NON-NLS-1$ } } public void contextDestroyed(ServletContextEvent sce) { if (!performShutdown) return; try { if (scheduler != null) scheduler.shutdown(); } catch (Exception e) { logger.error("Quartz Scheduler failed to shutdown cleanly: " + e.toString()); e.printStackTrace(); } logger.info("NotificationServiceManager - Quartz Scheduler successful shutdown."); } }
Normalmente, el scheduler se pasa al resto de la webapp via el contexto. En mi caso, dado que mi aplicación es un servicio web, he creado una instancia singleton de una clase AdminHelper a la que uso para almacenar todos los datos de configuración de mi aplicación. En este caso, aprovecho AdminHelper para pasarle la instancia de Quartz que he iniciado en el listener.
Luego, dentro de mi aplicación podré obtener el shceduler de la siguiente manera:
Scheduler sched = (Scheduler) AdminHelper.getSf();
De esta forma, Quartz verá sin problemas a todas mis clases “job” dentro de la webapp.

Esta obra está bajo una licencia de Creative Commons.
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| « Apr | ||||||
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | |||