Integración de XML y los JavaBeans

Se dice que el Extensible Markup Language (XML), pronto sustituirá a HTML para muchas aplicaciones. En el capítulo anterior desarrollamos una clase para crear un JavaBean, en funcionamiento configurado desde el contenido de un fichero XML. En este capitulo vamos a desarrollar la inversa, es decir escribiremos una clase que escriba JavaBeans como ficheros XML, usando el mismo "dialecto" XML.

. Allanar Estructuras de Objetos

En la mayoría de los programas no triviales, se procesa la información usando estructuras de datos. En la programación orientada a objetos, estas estructuras de datos se componen generalmente de objetos que contienen los datos de interés para la aplicación. A menudo es deseable utilizar los datos en los objetos de la aplicación en más de una sesión del trabajo. (Sólo prueba a imaginar un procesador de textos sin un comando Save: apaga tu ordenador, y todos tus documentos morirían con el editor!) Persistencia es el término común para el modo en que un sistema de software hace que sus datos "sobrevivan" a la muerte de los procesos en los que se está ejecutando, de modo que él pueda vivir para ejecutarse otra vez otro día (o en otra máquina).

La persistencia ha estado siempre alrededor de la programación orientada a objetos -- por lo menos en la práctica, no en nombre. Los ficheros del procesador de textos, la peticiones de datos a través de una red, son ejemplos de persistencia. de hecho, la persistencia de los datos es la razón de todos los tipos de medios de datos que tenemos: de las antiguas tablillas de arcillas y los papiros , a las cintas de papel levemente más modernas, los carretes magnéticos, y a las tarjetas de perforadas de antaño, hasta los CD-ROMs y DVDs de hoy. La idea básica que hay detrás de la persistencia es codificar una estructura de software lógica en una secuencia de bytes (un proceso a veces llamado "aplanar") de una forma tal que la secuencia se pueda utilizar más adelante para reconstruir una estructura idéntica.

. Objetos Persistentes

Cuando el sistema de diseño orientado a objetos y la programación comenzaron a aparecer en la escena, estuvo inmediatamente claro (para cualquier persona que conocía dichas cosas) que sería muy conveniente poder crear objetos persistentes. Un objeto que se ejecutaba dentro de un programa podría ser convertido a una serie de bytes, y después ser salvado o transmitido, y esa serie de bytes podría ser utilizada más adelante y/o en otra parte para reconstruir el objeto. Esto es exactamente lo que lo hace la serialización Java. La base de Java tiene un conjunto de métodos incorporados (en el paquete java.io) que permite que un programador haga que un objeto se escriba a sí mismo en una secuencia de bytes, o leerse desde una secuencia de bytes. La persistencia en el contexto de los sistemas orientados a objetos comúnmente se llama persistencia de objetos; o, dicho de otra manera, los objetos que han "persistido" (observa que "persistir" se ha convertido repentinamente un verbo transitivo) se llaman "objetos persistentes".

Prácticamente todo sistema orientado a objetos tiene cierto mecanismo de persistencia de objetos, implicado generalmente en un formato de persistencia (la disposición de los bytes en la stream de datos) que es "estándar", por lo menos para esa aplicación. Se dice que lo más agradable sobre los estándares es que haya tantos donde elegir, y los formatos de persistencia de objetos no son ninguna excepción. Echa una mirada en la lista de "filtros" de documentos en un procesador de textos comercial si no te lo crees: cada uno de esos filtros está intentando convertir a partir desde un formato de persistencia propietario a el que se está usado internamente en el procesador de textos. El mecanismo de serialización de Java, de hecho, fue diseñado (mediante el interface java.io.Externalizable) para permitir que un programador lea y escriba los formatos de persistencia de los objetos de otras aplicaciones.

. Un Estándar Estándar

¿Qué tiene todo esto que ver con XML y JavaBeans? Bien, en estas páginas de XML y JavaBeans, estamos utilizando XML como formato de persistencia para los componentes JavaBeans. Lo agradable sobre XML, creo, es que es un estándar verdadero, pues la explosión de interés en los productos disponibles para XML lo demuestra. El estándar actual XML se llama Extensible Markup Language (XML) 1.0 W3C Recommendation. Cualquier aplicación que sea "compatible XML" (estó simplemente significa que la aplicación puede leer y escribir sus objetos en XML) potencialmente podrá interoperar con otros sistemas más fácilmente que antes de que XML estuviera disponible, porque todos los sistemas XML se adaptan el estándar (o no son compatibles con XML, por definición.)

Por ejemplo, un nuevo lenguaje de marcas personalizado llamado RDF, de "Resource Definition Framework" , se está estandarizando actualmente. Podemos leer la especificación RDF, que es un "dialecto" (técnicamente llamado una aplicación) de XML que se está definiendo para las descripciones generales de metadatos (datos sobre datos). Una vez que se estandardice este formato, toda aplicación que utilice el estándar podrá manipular y utilizar datos y metadatos de otros sistemas obedientes, porque todas las aplicaciones harán las mismas asunciones sobre lo que significan los datos.

Ahora, esto no significa necesariamente que toda aplicación entenderá siempre las etiquetas de marcas de todas las otras aplicaciones, como cualquier persona que intente escribir un navegador HTML-neutral puede decirnos. Siempre hay tensión entre la fabricación de un sistema extensible por un lado, y el mantenimiento de la compatibilidad por el otro. Pero XML proporciona un terreno común para los desarrolladores de sistemas con la persistencia de la estructura de objetos y publicar los interfaces en sus sistemas. Los lenguajes de marcas personalizados están apareciendo ya para dominios de aplicación tan variados como la química molecular, interfaces gráficos de usuario, y formularios comerciales. Cuando estos estándares lleguen a extenderse, la interoperation de los sistema llegará a ser más fácil para todos.

Observamos en el objeto Player de la página anterior que lo que leíamos desde un fichero de XML no era simplemente un objeto. Era un árbol: un objeto Player, que contenía referencias a un objeto Statistics, y a un objeto PersonName. Que toda la estructura de datos fue codificada en XML, y XMLBeanReader podía crear la estructura correspondiente en memoria examinando la estructura del objeto del Modelo del Objeto del Documento (DOM) creada por el analizador de sintaxis XML. Y, recordamos que no necesitamos escribir ningún código para el analizador. Simplemente le dimos al analizador de sintaxis XML el nombre del fichero XML, y éste devolvió la estructura de datos entera que el fichero de XML representaba. No estaba mal para una línea de código!!!

Así pues, XMLBeanReader nos da la capacidad de tomar una representación "plana" de una estructura del objeto (es decir, un JavaBean y sus propiedades representados como un stream de texto que resulta que es XML) y de crear la estructura correspondiente en memoria. Ahora, echemos una mirada a XMLBeanReader, que puede aceptar casi cualquier ejemplar JavaBean y representarlo en nuestro dialecto XML.

. Características de XMLBeanWriter

Antes de que nos zambullamos en escribir una clase para convertir un JavaBean en XML, hay un problema que deseo clarificar. El formato determinado del fichero XML que estamos utilizando para nuestro XML JavaBeans no es XML genérico. Es una aplicación de XML -- es decir, un dialecto XML que definimos nosotros mismos, simplemente creándolo.

Nuestro pequeño lenguaje XML JavaBeans es también muy simple: un elemento <JavaBean> contiene un elemento <Properties>, que a su vez contiene varios elementos <Property>. Un elemento <Property> podría contener un elemento de texto, indicando el valor de la propiedad, o un elemento <JavaBean>, si el valor de la propiedad es un JavaBean. Este es nuestro lenguaje de marcas personalziado. Por eso no podemos usar (actualmente) XMLBeanReader para leer XML genérico. La entrada XML tiene que adaptarse al "pequeño lenguaje" que hemos definido.

Una clase que lee las propiedades de un JavaBean y escribe el JavaBean en un fichero necesita poder hacer varias cosas. Aquí están las tareas que necesitamos realizar, junto con alguna explícación sobre cómo necesitamos que se haga:

  • Identificar la clase de un JavaBean.
    La clase de un JavaBean se puede identificar simplemente llamando a método getClass() del bean, que todo objeto Java debe tener. (Está definida en java.lang.Object, de la que desciende todo objeto.)

  • Identificar los nombres de las propiedades del JavaBean, sus tipos y sus valores.
    La obtención de los nombres de las propiedades de un JavaBean, sus tipos y sus valores es bastante fácil porque la mayoría del trabajo viene con la distribución básica de Java en la clase java.beans.Introspector y su subclase asociada java.beans.PropertyDescriptor.

  • Representar el valor de cada propiedad como XML.
    La representación de cada valor de propiedad como XML es un poco más difícil, porque todas las propiedades no se pueden representar como texto. Si una propiedad determinada no tiene ninguna representación de texto, pero el valor de la propiedad es un JavaBean, después podemos representar el valor de propiedad como un JavaBean codificado en XML. Este hecho indica que el código que codifica un JavaBean como XML debería ser un método independiente, para poderlo llamar recursivamente si el valor de una propiedad JavaBean es también un JavaBean.

  • Escribir el XML resultante en algún lugar.
    El lugar más simple para escribir la salida XML sería un fichero, pero esa solución no es muy general. ¿Qué pasaría si deseamos escribir el XML a una red, o a incluso a un buffer intermedio de memoria? Ambos escenarios parecen probables. Afortunadamente, la plataforma Java viene al rescate otra vez con el interface Writer, que cubriremos cuando discutamos en profundiad el código de abajo.

Ahora que hemos identificado lo que vamos a hacer, entremos en los detalles.

. Estructura del Programa

Para empezar, hemos resituado XMLBeanReader desde el paquete por defecto al paquete llamado com.javaworld.JavaBeans.XMLBeans, al que hemos añadido XMLBeanWriter, y al que añadiremos otras clases e interfaces posteriormente.

Podemos ver el todo código fuente de XMLBeanWriter en el fichero XMLBeanWriter.java, pero hemos situado algunas secciones de código en está página para poder explicarlas.

En el nivel más alto de abstracción, deseamos definir un método que escriba la representación XML de un JavaBean a un fichero, dando la clase del Bean y un nombre de fichero. Realmente implementamos tres métodos sobrecargados para flexibilidad añadida. El API base de Java proporciona un interface llamado java.io.Writer, que es una abstracción para escribir datos. La clase concreta java.io.FileWriter escribe datos a un fichero, pero otras subclases Writer permiten escribir a objetos String, buffers intermediarios, streams de impresión, pipes, etcétera. Esto es tan suficientemente general que ponemos la lógica real en un método llamado writeXMLBean, que toma el ejemplar del bean y un Writer como argumentos. Usando el interface java.io.Writer, XMLBeanWriter puede escribir cualquier clase que implemente java.io.Writer -- por ejemplo, una conexión de red. Dos métodos sobrecargados (significa que tienen el mismo nombre, pero distintos argumentos) aceptan un objeto File o un nombre de fichero String como indicación de donde irán los datos. Estos métodos son sólo de conveniencia, puesto que nuestro programa de ejemplo escribirá a un fichero. Hechemos una ojeada el código.

. Construir e Imprimir un Árbol de Documento

Hay tres métodos llamados XMLBeanWriter.writeXMLBean(), y dos de ellos simplemente llaman al tercero. El código de los tres métodos XMLBeanWriter.writeXMLBean() aparecen en la siguiente figura:

280 /** 
281  * Write a JavaBean as XML to a File.  
282  * @param bean The JavaBean to write 
283  * @param file The File to which to write the JavaBean. 
284  * @exception java.io.FileNotFoundException 
285  * @exception java.beans.IntrospectionException 
286  */ 
287 public static void writeXMLBean(Object bean, File file) 
            throws java.io.IOException, java.beans.IntrospectionException, 
288      InstantiationException, IllegalAccessException { 
289         writeXMLBean(bean, new FileWriter(file)); 
290     } 
291  
292  
293 /** 
294  * Write a JavaBean as XML to a Writer.  
295  * @param bean The JavaBean to write. 
296  * @param writer The writer to which the XML is written. 
297  * @exception java.beans.IntrospectionException 
298  * @exception IOException 
299  * @exception InstantiationException 
300  * @exception IllegalAccessException 
301  */ 
302 public static void writeXMLBean(Object bean, Writer writer) throws IOException, 
303                 java.beans.IntrospectionException, 
304                 InstantiationException, IllegalAccessException { 
305 	// Create a DOM document tree for this JavaBean and return it 
306 	// NOTE: This method specifically references TXDocument, 
307 	// which is an xml4j class! 
308 	TXDocument doc = new TXDocument(); 
309 	DocumentFragment df = getAsDOM(doc, bean); 
310 	doc.appendChild(df); 
311  
312 	// Write out the document as XML to the Writer. 
313 	// NOTE AGAIN: Specifically references TXDocument.printWithFormat(), 
314 	// which is xml4j-specific! 
315 	doc.printWithFormat(writer); 
316  
317 } 
318 /** 
319  * Write a JavaBean, formatted as XML, to a file whose name is passed as <strong>sFilename</strong>. 
320  * @param bean The JavaBean to write 
321  * @param sFilename The name or path to the file to write. 
322  * @exception java.io.FileNotFoundException  
323  * @exception java.beans.IntrospectionException  
324  */ 
325 public static void writeXMLBean(Object bean, String sFilename) 
		throws java.io.IOException, java.beans.IntrospectionException, 
326 InstantiationException, IllegalAccessException { 
327 	writeXMLBean(bean, new FileWriter(sFilename)); 
328 } 

Figura 1: Métodos XMLBeanWriter.writeXMLBean()

La primera versión del método (líneas 287 a 290) toma un nombre de fichero String, luego simplemente crea un objeto File, y lo pasa a la tercera versión del método (líneas 325 a 329) que usa su argumento File para construir un FileWriter, y pasar el resultado a la segunda versión.

Es la segunda versión de writeXMLBean (líneas 302 a 317) la que realmente hace el trabajo. Esta clase writeXMLBean podría muy fácilmente escribir su XML al Writer, siguiendo la pista a la identación etc. Pero pensemos en esto por un momento: un documento XML en un programa se puede representar como un árbol de nodos del modelo de objeto del documento (DOM), ¿correcto? Y deseamos aplanar este árbol de nodos DOM y escribirlo a un fichero de texto. Bien, ¿por qué no tener un método que construya realmente el árbol DOM que representa el JavaBean, y entonces otro que imprima el árbol DOM como XML? Podríamos exponer los métodos (que verémos abajo) que convierten un JavaBean en un árbol, y después convertimos el árbol a texto, que entonces se escribe a un fichero.

Por ejemplo, imaginemos que tenemos un JavaBean que se parece al de la figura 2.


Figura 2: Un árbol de JavaBean

El JavaBean de la clase Grade tiene dos propiedades: un float llamado "promedio", y un JavaBean de la clase Course llamado "curso". (recuerda que el valor de una propiedad JavaBean puede en sí mismo ser un JavaBean.) La clase XMLBeanWriter internamente construirá un árbol DOM para este JavaBean que se parecerá a la figrua 3.


Figura 3: Un árbol de DOM que representa nuestro JavaBean.

El XML correspondiente al árbol de la Figura 3 aparece en la figura 4:

<JavaBean CLASS="Grade">
<Properties>
	<Property NAME="average">97.2</Property>
	<Property NAME="course">
	<JavaBean CLASS="Course">
		<Properties>
			<Property NAME="number">203</Property>
			<Property NAME="description">Basketry</Property>
		</Properties>
    	</JavaBean>	
	</Property>
</Properties>
</JavaBean>

Figura 4: XML del Árbol DOM.

Hay tres excelentes razones para construir un árbol y luego imprimirlo, en lugar de sólo imprimir XML mientras analizamos el JavaBean.

  1. Primero, si tenemos un método que convierta un JavaBean a un árbol DOM, entonces cualquier otro programa de Java de "cortar-y-pegar" que utilice los árboles DOM puede utilizar nuestra clase para conseguir una representación DOM del JavaBean. Lo que hace que la clase XMLBeanWriter generalmente sea más útil.

  2. La segunda razón para crear un árbol DOM y luego imprimirlo es que las implementaciones DOM incluyen un método que imprime el árbol DOM como XML, y así tenemos hecho parte del trabajo.

  3. La tercera razón es que el cear un árbol DOM nos da a nosotros algo sobre lo que escribir, y a te da la oportunidad de aprender a manipular documentos XML en Java.

Ahora que tenemos una idea de qué es lo que hace el código, echemos un vistazo al propio código fuente. El codigo de todas las versioens de XMLBeanWriter aparece abajo en la Figura 5:

Volviendo a la tercera versión de writeXMLBean, observa que primero creamos un TXDocument llamado doc, como este:

TXDocument doc = new TXDocument();

Un TXDocument es una implementación específica del interface org.w3c.dom.Document (o, sólo Document). Document es sólo un interface definido por el W3C. No tiene ninguna implementación determinada, pero ha definido el comportamiento, que resume qué es un interface. TXDocument es una clase del paquete xml4j de IBM que implementa el interface Document. Es la raíz del árbol DOM del documento . El interface Document (puedes leer sobre él en http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#i-Document) define un API que permite que un programador agregue, suprima, e itere sobre los elementos hijos del árbol DOM del documento.

Después de haber creado el Document que vamos a imprimir, llamamos al método getAsDOM(), que construye un árbol DOM de documento basándose en las propiedades del JavaBean.

309 DocumentFragment df = getAsDOM(doc, bean);
310 doc.appendChild(df);

Aquí hay dos cosas interesantes. La primera es que getAsDOM() devuelve un DocumentFragment, que requiere muy poca explicación. ¿Recuerdas de antes, donde explicamos el recorte y pegado de piezas de árboles del documento? Bien, un DocumentFragment es sólo lo que su nombre implica: es un pequeño pedazo de un documento que se pueda agregar a la lista de hijos de cualquier Node DOM. Un DocumentFragment sea un contenedor de peso ligero que, cuando es agregado a los Nodes hijos de un DOM le da todos sus hijos a ése Node, pero no aparece como un hijo del propio Node. Así pues, en la segunda línea de arriba, cuando se añade el DocumentFragment al final de la lista de hijos del Document, el Document no tiene un hijo que sea DocumentFragment; en vez de eso, tiene cualquier hijo que tuviera el DocumentFragment. (El DocumentFragment permanece sin cambiar: "no pierde" a sus hijos Document.) Nosotros utilizamos los objetos DocumentFragment extensivamente en WriteXMLBean.

La segunda cosa interesante sobre la llamada a getAsDOM() es que estamos pasando el objeto Document al método. Hacemos esto por una razón muy específica: los únicos objetos que se pueden agregar a un objeto Document son los objetos que el mismo Document ha creado. Observaremos que hace algunas líneas, creamos un ejemplar de TXDocument. Esta es una implementación específica de un objeto DOM implementado por IBM. Los otros objetos que pueden aparecer en un árbol DOM -- Comment, Element, Text, etc. -- también deben ser creados. En vez de las declaraciones new() por todas partes, cada de las cuales tendría que ser modificada si las clases que implementaban el DOM se modificaran, Document define una lista de métodos que realizan la creación de los objetos secundarios del árbol, sobre peticiones. Así pues, si quisieramos que un objeto del nodo Text agregara algún nodo al árbol DOM que estámos construyendo, en vez de decir:

Text tx = new TXText("FiddleFaddle");

le pediríamos al Document que creara uno por nosotros:

Text tx = doc.createTextNode("FiddleFaddle");

Los otros tipos de objetos DOM se crean de forma similar, veremos ejemplos más abajo.

Los métodos create del interface Document son un excelente ejemplo del modelo de diseño Factory, donde la creación de un objeto se difiere a otro objeto. Si cambiamos a otra implementación de DOM, sólo tendremos que cambiar el new(TXDocument). Todos los otros objetos se crearán desde la nueva clase Document.

El uso de este patrón de diseño de Factorías proporciona otros beneficios que son poderosos pero se salen del ámbito de este tutorial.

La línea final del método writeXMLBean llama al método sobre el que hablamos antes, en el que el objeto TXDocument escribe su árbol en el Writer en formato XML:

doc.printWithFormat(writer);

Ya hemos visto el marco de trabajo , ahora veamos cómo getAsDOM() crea un árbol DOM.

. Anatomía de un Árbol Bean

Hay dos versiones de getAsDOM(), la primera de las cuales aparece en la figura 5:

032 public static DocumentFragment getAsDOM(Document doc, Object bean) 
		throws IntrospectionException, InstantiationException, IllegalAccessException { 
033  
034 	// Create the fragment we'll return. 
035 	DocumentFragment dfResult = null; 
036  
037 	// Analyze the bean. 
038 	Class classOfBean = bean.getClass(); 
039  
040 	// If the bean knows how to encode itself in XML, then 
041 	// use the DOM document it returns. 
042 	try { 
043		Method mgetAsDOM = classOfBean.getMethod("getAsDOM", 
044		new Class[] { org.w3c.dom.Document.class } ); 
045		dfResult = (DocumentFragment) mgetAsDOM.invoke(bean, 
046		new Object[] { doc }); 
047 	} catch (Exception e) { 048;; // Ignore exceptions 
049 	} 
050  
051 	// If the bean doesn't know how to encode itself in XML, 
052 	// then create a DOM document by introspecting it. 
053 	if (dfResult == null) { 
054		dfResult = doc.createDocumentFragment(); 
055		BeanInfo bi = Introspector.getBeanInfo(classOfBean); 
056		PropertyDescriptor[] pds = bi.getPropertyDescriptors(); 
057  
058		// Add an Element indicating that this is a JavaBean. 
059		// The element has a single attribute, which is that Element's class. 
060		Element eBean = doc.createElement("JavaBean"); 
061		dfResult.appendChild(eBean); 
062		eBean.setAttribute("CLASS", classOfBean.getName()); 
063     Element eProperties = doc.createElement("Properties"); 
064     eBean.appendChild(eProperties); 
065  
066// For each property of the bean, get a DocumentFragment that 
067// represents the individual property. Append that DocumentFragment 
068// to the Properties element of the document 
069     for (int i = 0; i < pds.length; i++) { 
070         PropertyDescriptor pd = pds[i]; 
071         DocumentFragment df = getAsDOM(doc, bean, pd); 
072         if (df != null) { 
073             // Create a Property element and add to Properties element 
074             Element eProperty = doc.createElement("Property"); 
075             eProperties.appendChild(eProperty); 
076  
077             // Create NAME attribute, add it to Property element, 
078             // and set it to name of property 
079             eProperty.setAttribute("NAME", pd.getName()); 
080  
081             // Append the DocumentFragment to the Property element 
082             // This "splices" the entire DOM representation of the 
083             // Property into the tree at this point. 
084             eProperty.appendChild(df); 
085         } 
086     } 
087 } 
088 return dfResult; 
089 } 

Figura 5: Primera versión de getAsDOM().

Lo primero que hace getAsDOM(), a excepción de obtener la clase Bean para su uso posterior, es controlar para ver si el bean define un método llamado DocumentFragment getAsDOM(org.w3c.Document) (líneas 42 a 49). Si es así invoca a ese método en el Bean, y selecciona el DocumentFragment resultante (el subdocumento que estamos construyendo) a lo que ese método devuelve. Hace esto para permitir que un desarrollador reemplace la forma estándar de representar este ejemplar del objeto como XML. Éste es una convención que writeXMLBean define para agregar flexibilidad.

Esencialmente, WriteXMLBean.getAsDOM() le está "pidiendo" al JavaBean, "hey, Tu! ¿sabes representarte como un documento DOM?" La respuesta es "sí" si la clase del Bean define este método. Esto hace a XMLBeanWriter más flexible. Por ejemplo, si estámos escribiendo un JavaBean que deseamos convertir en XML, pero deseamos un cierto control sobre cómo ese Bean se representa en XML (digamos que no te gusta como lo hemos hecho), todavía podemos utilizar XMLBeanWriter. Simplemente definimos nuestro propio método getAsDOM() en nuestra clase JavaBean, y XMLBeanWriter.getAsDOM() devuelve cualquier DocumentFragment que devolaamos. Veremos un ejemplo de esto de nuevo en la sección Representar como XML .

Como ejemplo, hemos modificado la clase Player.java para definir su propio getAsDOM(). Digamos que el hipotético Bean del jugador de baseball (en Player.java) de nuestro ejemplo de la página anterior con la propiedad gradePointAverage, pero por razones de privacidad, queremos excluir este número del documento DOM resultante. El código aparece en la Figura 7:

024 public DocumentFragment getAsDOM(Document doc) { 
025 	DocumentFragment df = doc.createDocumentFragment(); 
026  
027 	// Create the entire document for the Bean in code. 
028  
029 	Element eJavaBean = doc.createElement("JavaBean"); 
030 	eJavaBean.setAttribute("CLASS", "Player"); 
031 	Comment comment = doc.createComment("XML for this Player created by Player.getAsDOM()"); 
032 	eJavaBean.appendChild(comment); 
033 	Element eProperties = doc.createElement("Properties"); 
034 	eJavaBean.appendChild(eProperties); 
035 	Element eProperty; 
036 	eProperty = doc.createElement("Property"); 
037 	eProperty.setAttribute("NAME", "highSchool"); 
038 	eProperty.appendChild(doc.createTextNode(getHighSchool())); 
039 	eProperties.appendChild(eProperty); 
040 	eProperty = doc.createElement("Property"); 
041 	eProperty.setAttribute("NAME", "number"); 
042 	eProperty.appendChild(doc.createTextNode(Integer.toString(getNumber()))); 
043 	eProperties.appendChild(eProperty); 
044 	eProperty = doc.createElement("Property"); 
045 	eProperty.setAttribute("NAME", "name"); 
046 	PersonName pnName = getName(); 
047 	if (pnName != null) { 
048             DocumentFragment dfName = pnName.getAsDOM(doc); 
049             eProperty.appendChild(dfName); 
050             eProperties.appendChild(eProperty); 
051 	} 
052 	eProperty = doc.createElement("Property"); 
053 	eProperty.setAttribute("NAME", "stats"); 
054 	try { 
055             DocumentFragment dfStats = com.javaworld.JavaBeans.XMLBeans.XMLBeanWriter.getAsDOM(doc, 
                                                                    (Object)(getStats())); 
056             eProperty.appendChild(dfStats); 
057             eProperties.appendChild(eProperty); 
058 	} catch (Exception ee) { 
059             // If an exception occurs, the property is simply ignored. 
060 	} 
061 	df.appendChild(eJavaBean); 
062 	return df; 
063 } 

Figura 7: El Bean Player.

En la figura 7, el nuevo método getAsDOM() realmente construye un subdocumento DOM en el código, y lo devuelve como un DocumentFragment. Observa que ignora la propiedad gradePointAverage que el mecanismo estándar habría hecho salir. También llama a PersonName.getAsDOM(). El XML que resulta de imprimir el árbol del documento aparece en la Figura 8 abajo. (Bean1.xml, es la entrada de esta ejecución.)

<JavaBean CLASS="Player">
<!--XML for this Player created by Player.getAsDOM()-->
<Properties>
	<Property NAME="highSchool">Eaton</Property>
	<Property NAME="number">12</Property>
	<Property NAME="name">
		<!--XML for this PersonName created by PersonName.getAsDOM()-->
		First:     
		Jonas, Last: 
    	Grumby
	</Property>
	<Property 
    	NAME="stats">
		<JavaBean CLASS="Statistics">
		<Properties>
			<Property NAME="year">1997</Property>
			<Property NAME="atBats">69</Property>
			<Property NAME="hits">30</Property>
			<Property NAME="homeRuns">2</Property>
			<Property NAME="runsBattedIn">15</Property>
			<Property NAME="runs">31</Property>
		</Properties>
		</JavaBean>
	</Property>
</Properties>
</JavaBean>

Observamos cómo el comentario incluido en Player.getAsDOM() dio lugar a un comentario en la salida. Observamos también la ausencia de cualquier promedio . Esta técnica es de gran alcance porque le da al programador que usa el XMLBeanWriter un completo control sobre la estructura de la salida. Una forma más elegante de hacer esto, implicando PropertyDescriptors, aparecerá en la página siguiente

Volvamos de nuevo a mirar el código. Refiriendonos a la Figura 5 otra vez, las líneas 54 a 55 hacen que el Bean consiga su objeto BeanInfo, y solicitándole la lista de objetos PropertyDescriptor del bean. En este punto, el programa conoce qué propiedades va a agregar al Document.

Las líneas 58 a 64 son nuestro primer ejemplo de creacción de objetos DOM y añadirlos al árbol:

058 // Add an Element indicating that this is a JavaBean.
059 // The element has a single attribute, which is that Element's class.
060 Element eBean = doc.createElement("JavaBean");
061 dfResult.appendChild(eBean);
062 eBean.setAttribute("CLASS", classOfBean.getName());
063 Element eProperties = doc.createElement("Properties");
064 eBean.appendChild(eProperties);

La línea 60 crea un elemento que se parece a <JavaBean> en XML. La siguiente línea (61) la agrega al árbol resultante. Entonces el método fija el atributo CLASS del elemento JavaBean al nombre de la clase del Bean (línea 62), así que el XML que resulta parecería <JavaBean CLASS="classname">. Las dos líneas siguientes (63 a 64) crean un elemento <Properties> y lo añaden al elemento <JavaBean>. En este punto, el árbol se parece a esto:

<JavaBean CLASS="classname">
<Properties>
</Properties>
</JavaBean>

Por supuesto, el documento "realmente" no se parece a esto, En realidad, esto sólo una estructura de datos. El XML de arriba es lo que veríamos si imprimieramos el fragmento de documento en este punto.

El bucle de la parte inferior del método (líneas 69-86) itera sobre la lista de descriptores de propiedades, y por cada propiedad:

  • obtiene el XML de cada propiedad llamando al método getAsDOM() sobrecargado para propiedades.

  • crea un elemento <Property> para la propiedad.

  • Fija el atributo NAME del elemento <Property> al nombre de la propiedad.

  • Añade la estructura DOM de la propiedad al elemento <Property>.

  • Añade el nuevo elemento <Property> al elemento <Properties>.

La estructura resultante es la representación DOM del JavaBean. Ahora todo lo que nos falta es entender cómo se representa una propiedad como XML.

. Representar propiedades como XML

El código para la segunda versión de getAsDOM() aparece en la figura 9. Este método genera el XML para la propiedad JavaBean indicada por el tercer argumento del método, un PropertyDescriptor. Este método podría devolver null, para indicar que no puede imaginarse cómo representar la propiedad como XML.

102public static DocumentFragment getAsDOM(Document doc, Object bean, PropertyDescriptor pd) 
103  	    throws IntrospectionException, InstantiationException, IllegalAccessException { 
104 	Class classOfBean = bean.getClass(); 
105 	Class classOfProperty = pd.getPropertyType(); 
106 	DocumentFragment dfResult = null; 
107 	String sValueAsText = null; 
108 	Class[] paramsNone = {}; 
109 	Object[] argsNone = {}; 
110  
111 	// If the property is "class", and the type is java.lang.class, then 
112 	// this is the class of the bean, which we've already encoded. 
113 	// So, in this special case, return null. 
114 	if (pd.getName().equals("class") && classOfProperty.equals(java.lang.Class.class)) { 
115         return null; 
116 	} 
117  
118 	// 1. Try to represent the property as XML. 
119 	// This bean may know how to describe itself, or parts of 
120 	// itself, as XML. There are two possibilities: 
121 	// [a] The bean has a method called get<Propname>AsXML() 
122 	// [b] The property class has a method called getAsDOM() 
123 	// We'll try both of these, and the first (if any) that 
124 	// works will be the DocumentFragment we want to return. 
125 	// If none of these are true, then we try to find the object's 
126 	// value as text. 
127  
128 	// [1a] Does the bean have a method called get<Propname>AsXML()? 
129 	// Capitalize property name. 
130 	StringBuffer sPropname = new StringBuffer(pd.getName()); 
131 	char c = sPropname.charAt(0); 
132 	if (c >= 'a' && c <= 'z') { 
133             c += 'A' - 'a'; 
134 	} 
135 	sPropname.setCharAt(0, c); 
136 	String sXMLGetterName = "get" + sPropname + "AsXML"; 
137  
138 	// If both of these methods succeed, then dfResult will be set 
139 	// to non-null; that is, the method existed and returned a 
140 	// DocumentFragment. 
141 	try { 
142             Class [] params = { org.w3c.dom.Document.class }; 
143             Method mXMLGetter = classOfBean.getMethod(sXMLGetterName, params); 
144             Object[] args = { doc }; 
145             dfResult = (DocumentFragment) (mXMLGetter.invoke(bean, args)); 
146 	} catch (Exception ee) { 
147            ;// Ignore... couldn't get the method 
148 	} 
149  
150 	// Hereafter, we're trying to create a representation of the property 
151 	// based somehow on the property's value. 
152 	// The very first thing we need to do is get the value of the 
153 	// property as an object. If we can't do that, we can get no 
154 	// representation of the property at all. 
155 	Object oPropertyValue = null; 
156 	try { 
157             Method getter = pd.getReadMethod(); 
158             if (getter != null) { 
159                 oPropertyValue = getter.invoke(bean, argsNone); 
160             } 
161         } catch (InvocationTargetException ex) { 
162             ; // Couldn't get value. Probably should be an error. 
163  	} 
164  
165 	// [1b] If we don't have a DocumentFragment, the previous block failed. 
166 	// So, let's find out if the property's class has a method called 
167 	// getAsDOM() and, if it does, call that instead. 
168 	if (dfResult == null) { 
169             try { 
170                 Class [] params = { org.w3c.dom.Document.class }; 
171                 Method mXMLGetter = classOfProperty.getMethod("getAsDOM", params); 
172                 Object[] args = { doc }; 
173                dfResult = (DocumentFragment) (mXMLGetter.invoke(oPropertyValue, args)); 
174             } catch (Exception ee) { 
175                 ; // Ignore -- who cares why it failed? 
176             } 
177      	} 
178  
179  
180 	// 2. Try to represent the property as a String. 
181 	// See if this property's value 
182 	// is something we can represent as Text, or if it's something 
183 	// that must be represented as a JavaBean. Let's assume that this 
184 	// object can be represented as text if: 
185 	// [a] it has a PropertyEditor associated with it, because 
186 	// PropertyEditors always have setAsText() and getAsText(). 
187 	// If it can't be represented as text, then we pass it to 
188 	// getAsDOM(Document, Object) and return the result. 
189  
190 	if (dfResult == null) { 
191  
192             // [2a] Can we get either a custom or built-in property editor? 
193             // If the PropertyDescriptor returns an editor class, we 
194             // create an instance of it; otherwise, we ask the system for 
195             // a default editor for that class. 
196             Class pedClass = pd.getPropertyEditorClass(); 
197             PropertyEditor propEditor = null; 
198             if (pedClass != null) { 
199                 propEditor = (PropertyEditor) (pedClass.newInstance()); 
200             } else { 
201                 propEditor = PropertyEditorManager.findEditor(classOfProperty); 
202             } 
203  
204             // If the property editor's not null, pass the property's 
205             // value to the PropertyEditor, and then ask the PropertyEditor 
206             // for a text representation of the object. 
207             if (propEditor != null) { 
208                 propEditor.setValue(oPropertyValue); 
209                 sValueAsText = propEditor.getAsText(); 
210             } 
211  
212             // If somewhere above we found a string value, then create 
213             // a DocumentFragment to return, and append to it 
214             // a Text element. 
215             if (sValueAsText != null) { 
216                 dfResult = doc.createDocumentFragment(); 
217                 Text textValue = doc.createTextNode(sValueAsText); 
218                 dfResult.appendChild(textValue); 
219             } 
220  	} 
221  
222  	// 3. Try to represent the property value as a JavaBean. 
223 	// If we don't have a DocumentFragment yet, we'll 
224 	// have to introspect the value of the object, because 
225 	// it's apparently something that can't be represented 
226 	// as flat text. We'll assume it's a JavaBean. 
227 	// If it isn't... oh, well. 
228 	// 
229 	if (dfResult == null) { 
230  	    dfResult = getAsDOM(doc, oPropertyValue); 
231 	} 
232 	return dfResult; 
233    } 

Figura 9: Segunda versión de GetAsDOM().

Vayamos a través de esta segunda versión del método getAsDOM() y veamos que hace:

  • Chequea para ver si la propiedad es la Clase del Bean --
    Lo primero que hace getAsDOM() (líneas 114 - 116) es chequear si el nombre de la propiedad es class y el tipo de la propiedad es java.lang.Class, y si así, devuelve null. Esto es porque el Introspector (a menos que el BeanInfo diga lo contrario) observa que el JavaBean tiene un método llamado getClass() que devuevle un Class. Por eso, por definición, trata la clase del objeto como una propiedad. Para la mayoría de los Beans, se le pedirá a este método que cree XML para la clase del objeto. Esto no es una propiedad válida para nuestros propósitos (nos ocupamos de la clase de la propiedad preguntándole la Máquina Virtual Java (JVM) sobre ella, no leyéndolo desde el XML), así que decimos al llamador que ignore esta propiedad devolviendo null.

  • Chequea para ver si el Bean proporciona XML personalizado para una propiedad --
    Las siguientes líneas (130 a 136) controlan para ver si el Bean tiene un método llamado getPropertynameAsDOM(). Esta es la comprobación que mencionamos arriba para el getAsDOM(), excepto que comprueban una propiedad individual en vez de un Bean entero. Buscando un método con un nombre particular, de esta forma define una convención de nombramdo que XMLBeanWriter utiliza para hacer la programación fácil y flexible. Si un Bean tiene una propiedad Price, por ejemplo, un programador puede definir int getPrice() y void setPrice(int price) normalmente, pero después también define getPriceAsDOM(), y devuelve un fragmento DOM arbitrariamente complejo cuando este pedazo de código lo solicita. Hemos creado un nuevo tipo de accesor de propiedades, que consigue una propiedad no como valor, sino como un árbol DOM. Por supuesto, necesitamos modificar XMLBeanReader para manejar un accesor de setPropertynameAsDOM. Lo abordaremos en la página siguiente.

  • Obtener el valor de propiedad --
    Si el Bean no define a un accesor para la propiedad como XML (llamémoslo "un accesor de propiedad XML"), entonces tendremos que imagirnarnos cómo representarlo nosotros mismos. En la Figura 9, las líneas 157 a 159 utilizan el método getter de la propiedad (que vino de PropertyDescriptor) para conseguir el valor de propiedad (en el Bean que estamos procesando) como un java.lang.Object. De aquí en adelante, cualquier representación de esta propiedad debe depender de tener un valor de propiedad -- porque de otra forma, ¿qué estámos formateando?

  • Ver si la clase de la propiedad sabe representarse como XML --
    La líneas 130 a 148 piden al PropertyDescriptor la clase de la propiedad, y entonces utilizan la reflexión para determinar si la clase de la propiedad define el método getAsDOM(). En la primera versión de getAsDOM() (Figura 5, el que obtiene el fragmento DOM para un Bean, en vez de una propiedad), buscamos un método getAsDOM() en la clase del Bean; aquí, estamos buscando uno en la clase de la propiedad. Como ejemplo de esto, agreguémos un método getAsDOM() al PersonName.java de la página anterior. Ahora, cuando un Player se está convirtiendo a XML, esta versión de getAsDOM() detecta que existe PersonName.getAsDOM(), y utiliza lo que ese método devuelve como la representación del name de la propiedad. Podemos ver los resultados de esta substitución en los comentarios y el formato del objeto PersonName en la Figura 8.

  • Intentar representar la propiedad como un Strring usando el PropertyEditor para la propiedad --
    Las líneas 190 a 220 intentan obtener una representación String cadena de la propiedad. La idea aquí es representar la propiedad como un sólo String, que se puede añadir al final del elemento <Property> como un nodo Text. El PropertyEditor para una propiedad, encontrado en el PropertyDescriptor, tiene métodos para fijar y obtener la propiedad como texto. Esto significa que podemos utilizar el PropertyEditor para convertir el objeto a y desde un String. Así pues, pedimos al PropertyDescriptor un PropertyEditor, y si esto no funciona, le pedimos al sistema (mediante un PropertyEditorManager) un editor por defecto para el tipo de la propiedad. Si conseguimos un editor de la propiedad de cualquiera de estas dos maneras, fijamos su valor (PropertyEditor.setValue()) al valor de la propiedad, y después conseguimos su valor como texto. Si todo esto tiene éxito, las líneas 215 a 219 crearán un nodo Text con el valor del texto de la propiedad dentro, y devuelven eso como la representación DOM de la propiedad.

  • Intentar representar la propiedad como un JavaBean --
    Si todos los otros métodos fallan, nosotros asumimos que el objeto devuelto es un JavaBean. Llamamos recursivamente a la primera versión de getAsDOM() sobre el valor de propiedad y esperamos por el mejor. Observa que no hay garantía de que cualquier clase sea un Bean. Lo que esto significa, si estámos definiendo propiedades que devuelven tipos no-primitivos, y no tenemos editor de propiedades y y no queremos que esas propiedades se pierdan, tendremos que definir un getAsDOM() para la propiedad, getPropertynameAsDOM() para la propiedad, o getAsDOM() para todo el Bean. Sin embargo, si la propiedad es un Bean, estas últimas líneas (líneas 229 a 231) crean correctamente el documento DOM para la propiedad, que entonces (como recordamos de la discusión de TXDocument arriba) se utiliza para escribir el XML al fichero de salida. Ya está!!!

. Conclusión

En esta página, hemos creado una clase que convierte un JavaBean en XML. En la siguiente página, miraremos (y corregiremos) algunos de los problemas que nuestro nuevo código causará en XMLBeanReader.

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.