Archivo

Archivo para Jueves, 18 de noviembre de 2004

Echando código: ¿Como hacer serialización en Java a la medida?

Jueves, 18 de noviembre de 2004



La verdad es que nunca me había tocado hacer serialización hasta que me topé con RMI; En ese caso todo se limitaba a implementar la interfaz ‘java.io.Serializable‘ y marcar algunos atributos como ‘trasient‘ los cuales no necesitaba el final y listo, mi clase estaba preparada para ser transportada por la Red (en este caso estaba trabajando en un proyecto de clasificados para automóviles y la forma más fácil y rapida de moverlos sin preocuparme por transacciones y serialización manual era esa).

Sin embargo, leyendo varias fuentes me encontré con varias razones para hacer mi propia serialización:

  1. Si el objeto tiene collecionesy son grandes en número, entonces quizas Java se pueda quedar sin memoria ya que tiene guardar todo el grafo de dependencias.
  2. Si la clase cambia de algún modo , entonces simplemente la des-serialización fallará ya que su serial interno será distinto (además de posibles cambios en la estructura)
  3. Quizas la representación de Java de el objeto serializado no es óptima en tamaño (Java seguro hace un trabajo razonable).

Asi que ¿como se hace?:

  1. Implemente la interfaz Serializable
  2. Marque aquellos campos que no son parte de la serialización como ‘trasient’
  3. Sobreescriba los métodos ‘writeObject’ y ‘readObject’
  4. !Listo!

Es sorprendente como el lenguaje pone a disposición una herramienta tan poderoza como la serialización; En otros lenguajes como C++, hay recurrer a liberías como Roguewave o Fast Objects de Poet para lograr el mismo objetivo (extra $$$$).

Pero bueno, esto está muy hablado; ¿Que tal si usted compila y corre el siguiente código de ejemplo que hice y se convence? (la clase se llama SimpleBean y es realmente simple, sólo con fines ilustrativos)

   1:import java.io.File;

2:import java.io.Serializable;
3:import java.io.ObjectOutputStream;
4:import java.io.ObjectInputStream;
5:import java.io.FileInputStream;
6:import java.io.FileOutputStream;
7:import java.io.IOException;
8:
9:/**
10: * This class shows how to use a custom serialization mechanism.
11: * @author Jose V Nunez Zuleta (josevnz@yahoo.com)
12: * @version 0.1
13: * @see http://java.sun.com/developer/technicalArticles/Programming/serialization/
14: * @see http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/serial-arch.html#wp5251
15: * @see http://www.javapractices.com/Topic45.cjp
16: */
17:public class SimpleBean implements Serializable {
18:
19: /**
20: * Constant, max age.
21: */
22: public static final int MAX_AGE=100;
23:
24: /**
25: * @serial Name of the user
26: */
27:
28: private String name;
29: /**
30: * @serial Last name of the user
31: */
32: private String lastname;
33:
34: /**
35: * @serial Age of the user
36: */
37: private int age;
38:
39: private transient final int maxAge = MAX_AGE;
40:
41: /**
42: * @serial brothers of the user
43: */
44: private String [] brothers;
45:
46: /**
47: * @serial brothers of the user
48: */
49: private long serialVersionUID = 20201973l;
50:
51: /**
52: * @serial The object resources were released early
53: */
54: private boolean isDestroyed;
55:
56: /**
57: * Parametric constructor.
58: * @param name User name
59: * @param lastname User last name
60: * @param age User age
61: * @param brothers Array of brothers (if any)
62: * @since 0.1
63: */
64: public SimpleBean(String name, String lastname, int age, String [] brothers) {
65: if (name == null) {
66: throw new NullPointerException();
67: }
68: this.name = name;
69: if (lastname == null) {
70: throw new NullPointerException();
71: }
72: this.lastname = lastname;
73: if ( (age < 0) || (age > MAX_AGE) ) {
74: throw new IllegalArgumentException();
75: }
76: this.age = age;
77: if (brothers == null) {
78: throw new NullPointerException();
79: }
80: this.brothers = new String[brothers.length];
81: System.arraycopy(brothers, 0, this.brothers, 0, this.brothers.length);
82: }
83:
84: /**
85: * Return a atring representation of the object
86: * @return String The format is maxAge, name, lastname, age, [brother1, brother2, ...]
87: * @since 0.1
88: */
89: public String toString() {
90: if (isDestroyed) {
91: throw new IllegalStateException("Cannot call this method after calling destroy!");
92: }
93: StringBuffer buffer = new StringBuffer();
94: buffer.append(maxAge);
95: buffer.append(", ");
96: buffer.append(name);
97: buffer.append(", ");
98: buffer.append(lastname);
99: buffer.append(", ");
100: buffer.append(String.valueOf(age));
101: buffer.append(", [");
102: for (int i=0; i < brothers.length; i++) {
103: buffer.append(brothers[i]);
104: if (i < brothers.length - 1) {
105: buffer.append(", ");
106: }
107: }
108: buffer.append("]");
109: return buffer.toString();
110: }
111:
112: /**
113: * Performs a deep comparison between objects of the same type
114: * @return boolean If is equal or not
115: * @since 0.1
116: */
117: public boolean equals(Object object) {
118: if (isDestroyed) {
119: throw new IllegalStateException("Cannot call this method after calling destroy!");
120: }
121: if (object instanceof SimpleBean) {
122: SimpleBean bean = (SimpleBean) object;
123: if (! bean.name.equals(name)) {
124: return false;
125: }
126: if (! bean.lastname.equals(lastname)) {
127: return false;
128: }
129: if (! (bean.age == age)) {
130: return false;
131: }
132: if (brothers.length != bean.brothers.length) {
133: return false;
134: }
135: for (int i=0; i < brothers.length; i++) {
136: if (! brothers[i].equals(bean.brothers[i])) {
137: return false;
138: }
139: }
140: return true;
141: } else {
142: return false;
143: }
144: }
145:
146: /**
147: * Custom serialization method for this class.
148: * @param output The object stream were the instance will be written to
149: * @throws IOException if there is an error writing the stream
150: * @serialData
151: * @since 0.1
152: */
153: private void writeObject(ObjectOutputStream output) throws IOException {
154: if (isDestroyed) {
155: throw new IllegalStateException("Cannot call this method after calling destroy!");
156: }
157: output.defaultWriteObject();
158: }
159:
160:
161: /**
162: * Custom de-serialization method for this class.
163: * @param inout The object stream from were the instance will be read
164: * @throws IOException if there is an error writing the stream
165: * @throws ClassNotFoundException
166: * @serialData
167: * @since 0.1
168: */
169: private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
170: if (isDestroyed) {
171: throw new IllegalStateException("Cannot call this method after calling destroy!");
172: }
173: input.defaultReadObject();
174: }
175:
176: /**
177: * Test method for the class
178: * @param args Command line arguments. arg[0] is the name of the serialized class and arg[1]
179: * is the type of operation(read or anything else for writting)
180: * @throws Exception On any fatal error
181: * @since 0.1
182: */
183: public static void main(String [] args) throws Exception {
184: if (! ((args != null) && (args.length == 2)) ) {
185: throw new NullPointerException("Please provide the name of the serialized object and if the option is read or write");
186: }
187: File file = new File(args[0]);
188: ObjectInputStream input = null;
189: ObjectOutputStream output = null;
190: String [] bro = { "pepe", "paco" };
191: SimpleBean instance = new SimpleBean("jose", "nunez", 31, bro);
192: System.out.println("Before serialization :" + instance);
193: try {
194: if (args[1].equals("read")) {
195: if (file.canRead()) {
196: input = new ObjectInputStream(new FileInputStream(file));
197: input.readObject();
198: System.out.println("After serialization :" + instance);
199: } else {
200: throw new IllegalArgumentException("Unable to open '" + file + "'");
201: }
202: } else {
203: output = new ObjectOutputStream(new FileOutputStream(file));
204: output.writeObject(instance);
205: }
206: } catch (Exception exp) {
207: throw exp;
208: } finally {
209: if (input != null) {
210: input.close();
211: }
212: if (output != null) {
213: output.close();
214: }
215: }
216: }
217:
218: /**
219: * Early resource liberation
220: */
221: public void destroy () {
222: if (! isDestroyed) {
223: for (int i=0; i < brothers.length; i++) {
224: brothers[i] = null;
225: }
226: isDestroyed = true;
227: }
228: }
229:
230: /**
231: * Last chance for resource liberation
232: */
233: protected void finalize() throws Throwable {
234: destroy();
235: super.finalize();
236: }
237:
238:}

Sin categoría

Opinión de Libro: Red Hat RPM Guide, de Eric Foster-Jhonson

Jueves, 18 de noviembre de 2004
Comentarios desactivados

Si usted es un desarrollador de software o es un administrador de sistemas seguramente ya le ha tocado lidiar con el problema de instalar aplicaciones de manera eficiente en Linux. En particular, Red Hat cuenta con un sistema sofisticado de empaquetado e instalación de aplicaciones llamado RPM. Pero un sistema tan completo como RPM también es muy complejo (tan intimidante a veces que las personas que no lo conocen y temen le llaman un ‘tar glorificado’); La documentación oficial es bastante confusa (toma tiempo acostumbrarse a ella) y muchas veces el ver otros archivos ‘.spec’ (el formato del archivo que permite crear el RPM) no ayuda mucho ya que muchas veces tienen errores o no aprovechan el potencial completo de la herramienta.

El libro, ”Red Hat RPM Guide, de Eric Foster-Jhonson”, es en mi opinión la mejor guia impresa acerca de este tópico; Es fácil de leer, concisa y con ejemplos suficientemente claros como para hacerla un libro de aprendizaje y de consulta. En particular mi capitulo preferido es el 11, titulado “Advanced RPM Packaging”.

El trabajar con RPM puede hacer que su software tenga un acabado mucho más profesional, sin mucho esfuerzo. En la compañia en donde trabajo logre convencer a mis jefes que usaran esta herramienta y ahora el software que hacemos para nosotros y otros cliente va en este formato (eso no significa que es perfecto. RPM es una herramienta compleja y toma tiempo aconstrumbrarse a sus “peculiaridades”).

¿No se convence? Bueno, como ejemplo yo le voy a mostrar como hacer una instalador de RPM para el servidor Jabber 2. El RPM va a estar entonado para una instalación en Red Hat Enterprise Server 3, y además va a tener un script hecho en Bash que le va a permitir arracar y detener el demonio cada vez que la maquina sea reiniciada.

Busquese una buena tasa de café Venezolano, porqué este tutorial es algo largo. Pero estoy seguro que lo voy a convencer de que la herramienta es poderosa. También asumo que usted ya sabe programar algo en Bash, tiene conocimiento de permisologías bajo Unix y sabe como compilar una aplicación en C; Sino entonces busque las referencias apropiadas en Internet, usando Google por ejemplo.

Jabber es un servidor de mensajería instantáneos (Instant Messenger) con un protocolo abierto, basado en XML. La implementación del servidor Jabberd 2, no viene como un RPM sino en código fuente el cual deberemos compilar usando GCC para luego instalarlo en el sitio adecuado. Además de eso, necesitamos un script capaz de iniciar o detener el demonio el cual según los lineamientos de Red Hat deberá ser instalado en ‘/etc/init.d/jabberd‘ con permiso de ejecución y lectura para todo el mundo. El código del script se ve asi (Blogger le hace cosas estúpidas al código, asi que si lo necesita se lo puedo enviar por correo):

#!/bin/sh

#
# chkconfig: 345 26 74
# description: The Jabberd server is the original open-source server implementation of the Jabber protocol. # It remains the most popular software for deploying Jabber either inside a company or as a public IM service.
# processname: jabberd
# config: /opt/etc/jabberd
# author: josevnz@yahoo.com

if [ ! -f "/etc/sysconfig/jabberd" ]; then
printf "[ERROR:$LINENO] The main configuration file for Jabberd doesn't exist, '%s'\n" "/etc/sysconfig/jabberd"
exit 192
else
. /etc/sysconfig/jabberd
fi

# Source function library.
. /etc/init.d/functions

if [ ! -d "$CONF_DIR" ]; then
printf "[ERROR:$LINENO] The configuration directory for Jabberd doesn't exist, '%s'\n" $CONF_DIR
exit 192
fi

RETVAL=0

start() {
echo -n $"Starting up Jabberd daemon: "
for ((i=0; i < ${#PID_FILES[*]}; i++)); do
touch ${PID_FILES[$i]} && chown jabber:jabber ${PID_FILES[$i]}
done
su - jabber -c "$BIN_DIR/$DAEMON_NAME $OPTIONS &" > /var/log/jabberd.log 2>&amp;1
RETVAL=$?
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$DAEMON_NAME
echo
}

stop() {
echo -n $"Shutting down Jabberd daemon: "
for ((i=0; i < ${#PID_FILES[*]}; i++)); do
if [ -f "${PID_FILES[$i]}" ]; then
PID=`cat ${PID_FILES[$i]}`
if [ ! -z "$PID" ]; then
kill $PID
fi
# Save only the error status!
if [ $? -ne 0 ]; then
RETVAL=$?
fi
rm -f "${PID_FILES[$i]}" > /dev/null 2>&1
fi
done
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$DAEMON_NAME
echo
}

dostatus() {
for ((i=0; i < ${#DAEMONS[*]}; i++)); do
if [ ! -z "${DAEMONS[$i]}" ]; then
status ${DAEMONS[$i]}
fi
# Save only the error status!
if [ $? -ne 0 ]; then
RETVAL=$?
fi
RETVAL=$?
done
}

restart() {
stop
start
RETVAL=$?
}

condrestart() {
[ -e /var/lock/subsys/$DAEMON_NAME ] && restart || :
}

# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
dostatus
;;
restart|reload)
restart
;;
condrestart)
condrestart
;;
*)
echo "Usage: jabberd {start|stop|status|restart|reload|condrestart}"
exit 1
esac

exit $RETVAL

Además de esto, necesitará el código fuente de Jabber 2. Puede bajarselo de aqui.

Es mala idea instalar un RPM como el super usuario (root). En vez de eso, un RPM puede ser creado en su directorio hogar si usted le da las directivas adecuadas usando un archivo llamado ‘.rpmmacros’. Estos son los contenidos para mi directorio hogar en Linux:

# RPM macros file

#
%packager Jose Vicente Nunez Zuleta (josevnz@yahoo.com). RHCE, SJCD, SJCP, SCWCD
%distribution Redhat Linux
%vendor None
%url http://cvebrowser.sourceforge.net

%_topdir /home/josevnz/rpm

%_rpmtopdir %{_topdir}
%_builddir %{_rpmtopdir}/BUILD
%_rpmdir %{_rpmtopdir}/RPMS
%_sourcedir %{_rpmtopdir}/SOURCES
%_specdir %{_rpmtopdir}/SPECS
%_srcrpmdir %{_rpmtopdir}/SRPMS

Asegurece de que estos directorios existen antes de continuar; RPM por lo general espera que los archivos fuentes sean colocados en un directorio llamado ‘SOURCES’, en formato ‘.tar.gz’. Si ya se dió cuenta, el archivo ‘jabber’ que hice no es parte de el código original, asi que haciendo un poco de trampa podemos crear un archivo tar comprimido que contenga solo el archivo:

tar -czvf jabberd-init.tar.gz jabberd

Y luego copiamos el nuevo archivo al directorio ‘SOURCES’. En cuanto al archivo que utilizamos para hacer el RPM, este va por lo general en el directorio ‘SPECS’. Este archivo es el que controla como se hace la instalación:

# RPM Spec file for the Jabber 2 Instant Messenger daemon.

# This version of Jabber offers support for both MySQL and PAM on Red Hat Enterprise Server 3.
#
# RPM spec written by: Jose Vicente Nunez Zuleta (josevnz@yahoo.com), RHCE, SJCD, SJCP, SJCWD
#
%define _error_code 1
%define _ok_code 0
%define _installarchlib %(perl '-V:installarchlib'|cut -f2 -d'='|sed -e's#;##'|sed -e"s/'//g")
%define _installsitearch %(perl '-V:installsitearch'|cut -f2 -d'='|sed -e's#;##'|sed -e"s/'//g")
%define _installsitelib %(/usr/bin/perl '-V:installsitelib'|/bin/cut -f2 -d'='|/bin/sed -e's#;##'|/bin/sed -e"s/'//g")
%define _man3dir %(/usr/bin/perl '-V:man3dir'|/bin/cut -f2 -d'='|sed -e's#;##'|/bin/sed -e"s/'//g")
%define _man1dir %(/usr/bin/perl '-V:man1dir'|/bin/cut -f2 -d'='|sed -e's#;##'|/bin/sed -e"s/'//g")
%define _realname jabberd
%define _prefix /opt
%define _jabber_account_description Jabber
# The following properties can be defined on the rpm command line (--define)
# _prefix
# _topdir
# Check: http://www.rpm.org/hintskinks/unpackaged/
%define _unpackaged_files_terminate_build 0
%define _missing_doc_files_terminate_build 0
%define __find_provides /usr/lib/rpm/find-provides.perl

Summary: jabberd 2 is the next generation of the jabberd server.
Name: %{_realname}
Version: 2.0s4
Release: 4
Copyright: Creative Commons
Group: Instant Messenger Server
Source0: %{_realname}-%{version}.tar.gz
Source1: %{_realname}-init.tar.gz
URL: http://jabberd.jabberstudio.org/
Distribution: RedHat Enteprise Server 3
Vendor: Rob Norris (rob@cataclysm.cx).
Prefix: %{_prefix}
BuildRoot: /tmp/rpm-%{name}-root
BuildRequires: tar grep fileutils
Packager: Jose Vicente Nunez Zuleta (josevnz@yahoo.com), RHCE, SCJP, SCJD, SCWCD
Requires: openssl >= 0.9.7a, libidn >= 0.3.0, perl >= 5.6.1
BuildRequires: bash, openssl-devel >= 0.9.7a, libidn-devel >= 0.3.0, perl >= 5.6.1

%description
jabberd 2 is the next generation of the jabberd server.
It has been rewritten from the ground up to be scalable,
architecturally sound, and to support the latest protocol
extensions coming out of the JSF.

%prep
%setup -q -a1 -n %{_realname}-%{version}
export CFLAGS=-DOPENSSL_NO_KRB5
./configure --enable-mysql --enable-ssl --enable-idn --enable-pam --with-extra-include-path=/usr/include/openssl:/usr/include/mysql --with-extra-library-path=/usr/lib/mysql --enable-debug --prefix=%{buildroot}%{_prefix}/%{_realname}
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}%{_prefix}/%{_realname}
mkdir -p %{buildroot}%{_prefix}/%{_realname}/var/jabberd/pid/
mkdir -p %{buildroot}%{_prefix}/%{_realname}/var/jabberd/log/
mkdir -p %{buildroot}%{_prefix}/%{_realname}/var/jabberd/scripts/
make install
cp -p ./tools/db-setup.mysql \
%{buildroot}%{_prefix}/%{_realname}/var/jabberd/scripts/
mkdir -p %{buildroot}/etc/sysconfig
mkdir -p %{buildroot}/etc/init.d
cat <<> %{buildroot}/etc/sysconfig/%{_realname}
# Put here any options to be passed to the Jabber daemon
OPTIONS="-D"
# Place here the directory where all the XML configuration
# files are located.
declare -r CONF_DIR="%{_prefix}/%{_realname}/etc"
# Location of the binaries
declare -r BIN_DIR="%{_prefix}/%{_realname}/bin"
# Name to give to the daemon on the init script
declare -r DAEMON_NAME="%{_realname}"
# List of PID files to examine when killing jabberd
declare -a PID_FILES
PID_FILES[0]=/var/run/c2s.pid
PID_FILES[1]=/var/run/s2s.pid
PID_FILES[2]=/var/run/sm.pid
PID_FILES[3]=/var/run/resolver.pid
PID_FILES[4]=/var/run/router.pid
# List of daemons to check when Jabber is running
declare -a DAEMONS
DAEMONS[0]=c2s
DAEMONS[1]=s2s
DAEMONS[2]=sm
DAEMONS[3]=resolver
DAEMONS[4]=router
EOF
install %{_realname} %{buildroot}/etc/init.d
perl -p -i -e's#%{buildroot}##' %{buildroot}%{_prefix}/%{_realname}/bin/jabberd
perl -p -i -e's#%{buildroot}##' %{buildroot}%{_prefix}/%{_realname}/etc/jabberd/*
perl -p -i -e's#%{_prefix}/%{_realname}/var/jabberd/pid#/var/run#' %{buildroot}%{_prefix}/%{_realname}/etc/jabberd/*
%preun

%postun
echo "Please remember to delete the jabber user with 'userdel -r jabber'"
if [ -f /etc/init.d/jabberd ] && [ -x /etc/init.d/jabberd ]; then
/etc/init.d/jabberd stop
chkconfig --del jabberd
fi
%pre
if [ `cat /etc/passwd|grep jabber|wc -l` -eq 0 ]; then
useradd -c "%{_jabber_account_description}" -d %{_prefix}/%{_realname} jabber
else
printf "There is already a Jabber account present. Using that one instead\n"
fi
%post
chkconfig --add jabberd
ln -sf /var/lib/mysql/mysql.sock /tmp/mysql.sock
cp /etc/pam.d/system-auth /etc/pam.d/jabberd
echo "Please read how to change the configuration files, as indicated on 'http://jabberd.jabberstudio.org/2/docs/section04.html'"
%files
%defattr(-,jabber,jabber)
%doc %{_prefix}/%{_realname}/man
%config(noreplace) %attr(0750, jabber, jabber) %{_prefix}/%{_realname}/etc
%config(noreplace) %attr(0755, jabber, jabber) /etc/sysconfig/%{_realname}
%attr(0755, jabber, jabber) %{_prefix}/%{_realname}/bin
%{_prefix}/%{_realname}/var/jabberd/pid/
%{_prefix}/%{_realname}/var/jabberd/scripts/
%config(noreplace) %{_prefix}/%{_realname}/var/jabberd/log/
/etc/init.d/%{_realname}

%changelog
* Mon Nov 15 2004 Jose Vicente Nunez Zuleta 2.0s4
- First RPM release
%clean
rm -rf %{buildroot}

No voy a entrar mucho en detalle porque este no es un tutorial completo de RPM, pero le puedo adelantar algunas cosas:

  • Todo lo que está en color azul son declaraciones, variables global usadas durante la instalación (directorios de destino, dueños de archivos, permisología).
  • Todo lo que está en verde es la metadata del paquete: quien lo hizo, que se necesita para compilarlo, que se necesita para instalarlo (dependencias a otros RPMS claro está), quien lo empaqueto y de que se trata el paquete, en donde debe ir instalado.
  • Las secciones en purpura son los estados de la instalación. Usted puede pensar que un paquete de RPM pasa por los siguientes estados cuando se está compilando (esto es una simplicación, hay más cosas allí)
  1. Pre compilación
  2. Compilación e instalación
  3. Declaración de los archivos (cualos son documentación, cuales son configuración, cuales son binarios y en donde van).
  • Las secciones en rojo en cambio son usadas durante la fase de instalación (cuando ya el software está empaquetado en formato CPIO (rpm no utiliza tar para la distribución). Los nombre de cada etapa dan una indicación del proposito, por ejemplo ‘%pre‘ es para antes de instalar el RPM, ‘%postun‘ para después de una desinstalación, etc.

Para crear el RPM, entonces deberá escribir un comando como este:

[josevnz@god rpm]$ rpmbuild -ba SPECS/Jabber.spec

Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.33940

+ umask 022

+ cd /home/josevnz/rpm/BUILD

+ LANG=C

+ export LANG

+ unset DISPLAY

+ cd /home/josevnz/rpm/BUILD

+ rm -rf jabberd-2.0s4

+ /usr/bin/gzip -dc /home/josevnz/rpm/SOURCES/jabberd-2.0s4.tar.gz

+ tar -xf -



Checking for unpackaged file(s): /usr/lib/rpm/check-files /tmp/rpm-jabberd-root

Wrote: /home/josevnz/rpm/SRPMS/jabberd-2.0s4-4.src.rpm

Wrote: /home/josevnz/rpm/RPMS/i386/jabberd-2.0s4-4.i386.rpm

Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.32915

+ umask 022

+ cd /home/josevnz/rpm/BUILD

+ cd jabberd-2.0s4

+ rm -rf /tmp/rpm-jabberd-root

+ exit 0

Y para instalarlo:

rpm -ihv /home/josevnz/rpm/RPMS/i386/jabberd-2.0s4-4.i386.rpm

Bueno, eso es todo el tutorial :). Si desea ver más código, solo escribame y con gusto lo atenderé.

Sin categoría

Java y Geronimo: Como la fundación Apache puede cambiar el mercado de J2EE

Jueves, 18 de noviembre de 2004
Comentarios desactivados

Geronimo es la implementación gratuita de un contenedor de J2EE hecha por el mismo grupo que hizo el servidor web Apache; Si bien ya existe una versión gratuita llamada Jboss, la aparición de software soportado por Apache podría cambiar muchas cosas, entre ellas no sólo como los vendedores colocan el precio de la aplicación pero también como ciertos estandares son adoptados por Sun JCP.

¿No me cree aún?. Aqui le menciono una lista de software que yo conozco y que es patrocinado / desarrollado por el grupo:

(y esta es sólo una pequeña muestra ya que ellos tienen muchas más cosas disponibles).

finalmente, aqui hay otro articulo que habla del tema en detalle.

Sin categoría

A %d blogueros les gusta esto: