Skip to content

Commit e15d293

Browse files
committed
Pequeños cambios en la exposición de JAXP
1 parent c52a8b0 commit e15d293

File tree

4 files changed

+161
-136
lines changed

4 files changed

+161
-136
lines changed

source/02.formatos/02.json/01.jackson.rst

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,13 @@ en genéricos debemos recurrir a |TypeReference|:
7272

7373
.. code-block:: java
7474
75-
TypeReference<List<Grupo>> GrupoLista = new TypeReference<>(){};
76-
List<Grupo> grupos = mapper.readValue(sr, GrupoLista);
75+
List<Grupo> grupos = mapper.readValue(sr, new TypeReference<List<Grupo>>(){});
7776
78-
.. note:: Cuando los atributos de clases incluidas en la traducción
79-
son secuencias, es indiferente que sea listas o *arrays*: la traducción
80-
se lleva a cabo de igual forma. En nuestro ejemplo, es el caso del atributo
77+
.. note:: Este forma particular de tratar a la secuencia se produce cuando
78+
es la propia secuencia la raiz del archivo. Si las secuencias son valores de
79+
propiedades incluidas en el |JSON| es irrelevante que en las clases definamos
80+
el atributo correspondiente como un |List| o un ``Array``: la traducción se
81+
llevará a cabo de igual forma. En nuestro ejemplo, es el caso del atributo
8182
``miembros`` de :ref:`Grupo <class-grupo>`, que lo hemos definido como un
8283
*array*, pero podría perfectamente ser una lista.
8384

@@ -688,3 +689,5 @@ El otro mecanismo para filtrar atributos es usar **filtros**:
688689
.. |LocalDateDeserializer| replace:: :jackson-databind:`LocalDateDeserializer <ext/javatime/deser/LocalDateDeserializer>`
689690
.. |ObjectWriter| replace:: :jackson-databind:`ObjectWriter <ObjectWriter>`
690691
.. |TypeReference| replace:: :jackson-core:`TypeReference <type/TypeReference>`
692+
.. |List| replace:: :java-util:`List <List>`
693+

source/03.xml/01.jaxp.rst

Lines changed: 76 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -168,117 +168,6 @@ ejemplo, |Element|:
168168
devuelve el atributo (|Attr|) o el valor del atributo cuyo nombre se
169169
proporciona en el argumento.
170170

171-
.. _jaxp-dtd:
172-
173-
.. rubric:: Validación
174-
175-
Hemos evitado validar el documento hasta ahora. Sin embargo, la validación puede
176-
ser interesante o incluso, puede ser preciso, intentar que ni siquiera se haga
177-
ninguna comprobación en caso de que la declaración exista, pero el |DTD| no.
178-
179-
En primer lugar, si queremos que la validación se lleve a cabo debemos añadir:
180-
181-
.. code-block:: java
182-
183-
factory.setValidating(true);
184-
185-
Otro aspecto importante es el de la manipulación de la validación, que se lleva
186-
a cabo definiendo un *resolutor de entidades*.
187-
188-
.. code-block:: java
189-
:emphasize-lines: 1,3,4,9,10,15,16
190-
191-
builder.setEntityResolver(new EntityResolver2() {
192-
@Override
193-
public InputSource getExternalSubset(String name, String baseURI)
194-
throws SAXException, IOException {
195-
return null;
196-
}
197-
198-
@Override
199-
public InputSource resolveEntity(String publicId, String systemId)
200-
throws SAXException, IOException {
201-
return resolveEntity(null, publicId, null, systemId);
202-
}
203-
204-
@Override
205-
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId)
206-
throws SAXException, IOException {
207-
208-
if (systemId == null) return null;
209-
210-
try {
211-
systemId = resolvePath(baseURI, systemId);
212-
} catch(URISyntaxException e) {
213-
return null;
214-
}
215-
216-
return new InputSource(systemId);
217-
}
218-
219-
private String resolvePath(String base, String path) throws URISyntaxException {
220-
if(new URI(path).isAbsolute()) return path;
221-
if(base == null) throw new IllegalArgumentException("No puede calcularse una ruta relativa si la base es nula.");
222-
223-
if(base.contains("!")) {
224-
// Nos quedamos con la parte del path dentro del jar
225-
Path basePath = Path.of(base.substring(base.indexOf("!") + 1));
226-
// Resolvemos el path relativo y devolvemos el recurso.
227-
URL resource = getClass().getResource(basePath.resolveSibling(path).toString());
228-
return resource != null ? resource.toString() : null;
229-
}
230-
231-
return new URI(base).resolve(path).toString();
232-
}
233-
234-
});
235-
236-
Debemos fijarnos en el último método. Cuando devuelve `null`, es como, si no
237-
hubiéramos definido nada, y el procesador obrará como lo hace habitualmente para
238-
llevar a cabo la validación. En cambio, si devolvemos un :code:`new
239-
InputSource(cadena)` utilizará el |DTD| que indique esa cadena (puede ser una
240-
|URL| o un archivo local) con independencia de lo que expresase la declaración
241-
original.
242-
243-
.. admonition:: Advertencia
244-
245-
Este código da por hecho que se conoce la ruta del |XML| (``baseURI``). Esto,
246-
sin embargo, puede ser cierto o no. Todo este procesamiento lo desencadena
247-
:code:`builder.parse()`, el cual permite proporcionar los datos de distinto
248-
modo. Puede pasarse un |String| o un |File| en cuyo caso
249-
sí se sabrá ``baseURI``. En cambio, si se pasa un |InputStream| (que es
250-
*precisamente la forma en la que hemos ilustrado el uso*) no hay modo de
251-
conocer su valor y, en consencuencia, será ``null``. Eso sí, en este último
252-
caso, se admite un segundo argumento con una cadena que defina ``baseURI``.
253-
254-
¿Qué hace exactamente el código que hemos propuesto? En principio, si
255-
no hay definido ningun |DTD|, no hace nada. En cambio, si hay definido uno,
256-
comprueba si el |DTD| se proporciona con ruta absoluta. Si es así, respeta el
257-
valor y la validación se hará con el valor expresado en el archivo. En cambio,
258-
si la ruta es relativa, hace una distinción:
259-
260-
* Si ``baseURI`` contiene el carácter ``!`` se ha pasado como |XML| un
261-
:ref:`archivo de recursos <file-arc-pro>` y en consecuencia el |DTD| también
262-
lo será y hay que buscarlo dentro del paquete |JAR|.
263-
264-
* Si no es así, se resuelve de forma normal la ruta relativa usando como base el
265-
|XML| (``baseURI``).
266-
267-
.. tip:: El argumento del constructor de `InputSource` también puede ser un
268-
:java-io:`Reader <Reader>`, así que si nuestra intención es que el programa no
269-
escupa nunca un error (incluso aunque el |DTD| de la declaración no se
270-
encuentre), podemos hacer hacer lo siguiente:
271-
272-
.. code-block:: java
273-
274-
@Override
275-
public InputSource resolveEntity(String name, String publicID, String baseURI, String systemID)
276-
throws SAXException, IOException {
277-
return new InputSource(new StringReader(""));
278-
}
279-
280-
.. todo:: ¿Cómo forzar una validación, aunque no haya declaración DOCTYPE?
281-
282171
.. _jaxp-write:
283172

284173
Escritura
@@ -391,6 +280,82 @@ si nuestra intención es usarla una sola vez:
391280
int cantidad = ((Double) xPath.evaluate("count(//profesor)", xml, XPathConstants.NUMBER)).intValue();
392281
System.out.println(cantidad);
393282
283+
.. _jaxp-dtd:
284+
285+
Validación
286+
==========
287+
Hemos evitado validar el documento hasta ahora. Sin embargo, la validación puede
288+
ser interesante o incluso, puede ser preciso, intentar que ni siquiera se haga
289+
ninguna comprobación en caso de que la declaración exista, pero el |DTD| no.
290+
291+
En primer lugar, si queremos que la validación se lleve a cabo debemos añadir:
292+
293+
.. code-block:: java
294+
295+
factory.setValidating(true);
296+
297+
Otro aspecto importante es el de la manipulación de la validación, que se lleva
298+
a cabo definiendo un *resolutor de entidades*:
299+
300+
.. literalinclude:: files/MiEntityResolver.java
301+
:language: java
302+
:caption: MiEntityResolver.java
303+
:class: toggle
304+
:start-at: public class
305+
306+
Debemos fijarnos en el último método. Cuando devuelve `null`, es como, si no
307+
hubiéramos definido nada, y el procesador obrará como lo hace habitualmente para
308+
llevar a cabo la validación. En cambio, si devolvemos un :code:`new
309+
InputSource(cadena)` utilizará el |DTD| que indique esa cadena (puede ser una
310+
|URL| o un archivo local) con independencia de lo que expresase la declaración
311+
original.
312+
313+
Definido, podremos usarlo en el *builder* antes de procesar el documento:
314+
315+
.. code-block:: java
316+
317+
builder.setEntityResolver(new MiEntityResolver());
318+
Document xml = builder.parse(st);
319+
320+
.. admonition:: Advertencia
321+
322+
Este código da por hecho que se conoce la ruta del |XML| (``baseURI``). Esto,
323+
sin embargo, puede ser cierto o no. Todo este procesamiento lo desencadena
324+
:code:`builder.parse()`, el cual permite proporcionar los datos de distinto
325+
modo. Puede pasarse un |String| o un |File| en cuyo caso
326+
sí se sabrá ``baseURI``. En cambio, si se pasa un |InputStream| (que es
327+
*precisamente la forma en la que hemos ilustrado el uso*) no hay modo de
328+
conocer su valor y, en consencuencia, será ``null``. Eso sí, en este último
329+
caso, se admite un segundo argumento con una cadena que defina ``baseURI``.
330+
331+
¿Qué hace exactamente el código que hemos propuesto? En principio, si
332+
no hay definido ningun |DTD|, no hace nada. En cambio, si hay definido uno,
333+
comprueba si el |DTD| se proporciona con ruta absoluta. Si es así, respeta el
334+
valor y la validación se hará con el valor expresado en el archivo. En cambio,
335+
si la ruta es relativa, hace una distinción:
336+
337+
* Si ``baseURI`` contiene el carácter ``!`` se ha pasado como |XML| un
338+
:ref:`archivo de recursos <file-arc-pro>` y en consecuencia el |DTD| también
339+
lo será y hay que buscarlo dentro del paquete |JAR|.
340+
341+
* Si no es así, se resuelve de forma normal la ruta relativa usando como base el
342+
|XML| (``baseURI``).
343+
344+
.. tip:: El argumento del constructor de `InputSource` también puede ser un
345+
:java-io:`Reader <Reader>`, así que si nuestra intención es que el programa no
346+
escupa nunca un error (incluso aunque el |DTD| de la declaración no se
347+
encuentre), podemos hacer hacer lo siguiente:
348+
349+
.. code-block:: java
350+
351+
@Override
352+
public InputSource resolveEntity(String name, String publicID, String baseURI, String systemID)
353+
throws SAXException, IOException {
354+
return new InputSource(new StringReader(""));
355+
}
356+
357+
.. todo:: ¿Cómo forzar una validación, aunque no haya declaración DOCTYPE?
358+
394359
.. rubric:: Notas al pie
395360

396361
.. [#] En realidad el método tiene sentido para otros nodos que no sean
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import java.io.IOException;
2+
import java.net.URI;
3+
import java.net.URISyntaxException;
4+
import java.net.URL;
5+
import java.nio.file.Path;
6+
7+
import org.xml.sax.InputSource;
8+
import org.xml.sax.SAXException;
9+
import org.xml.sax.ext.EntityResolver2;
10+
11+
public class MiEntityResolver implements EntityResolver2 {
12+
@Override
13+
public InputSource getExternalSubset(String name, String baseURI)
14+
throws SAXException, IOException {
15+
return null;
16+
}
17+
18+
@Override
19+
public InputSource resolveEntity(String publicId, String systemId)
20+
throws SAXException, IOException {
21+
return resolveEntity(null, publicId, null, systemId);
22+
}
23+
24+
@Override
25+
public InputSource resolveEntity(String name, String publicId, String baseURI, String systemId)
26+
throws SAXException, IOException {
27+
28+
if (systemId == null) return null;
29+
30+
try {
31+
systemId = resolvePath(baseURI, systemId);
32+
} catch(URISyntaxException e) {
33+
return null;
34+
}
35+
36+
return new InputSource(systemId);
37+
}
38+
39+
private String resolvePath(String base, String path) throws URISyntaxException {
40+
if(new URI(path).isAbsolute()) return path;
41+
if(base == null) throw new IllegalArgumentException("No puede calcularse una ruta relativa si la base es nula.");
42+
43+
if(base.contains("!")) {
44+
// Nos quedamos con la parte del path dentro del jar
45+
Path basePath = Path.of(base.substring(base.indexOf("!") + 1));
46+
// Resolvemos el path relativo y devolvemos el recurso.
47+
URL resource = getClass().getResource(basePath.resolveSibling(path).toString());
48+
return resource != null ? resource.toString() : null;
49+
}
50+
51+
return new URI(base).resolve(path).toString();
52+
}
53+
}

source/04.conector/01.basico.rst

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Lo primero que debemos aprender es cómo abrir una conexión a la base de datos:
1010
.. code-block:: java
1111
:emphasize-lines: 1, 5, 11
1212
13-
final String protocol = "jdbc:sqlite:";
13+
final String dbProtocol = "jdbc:sqlite:";
1414
1515
// Las bases de datos de SQLite son archivos.
1616
Path dbPath = Path.of(System.getProperty("java.io.tmpdir"), "test.db");
@@ -69,7 +69,7 @@ Por supuesto, el código es *completamente inútil*: nos hemos conectado a la ba
6969
de datos para no hacer absolutamente nada. En los siguientes apartados veremos
7070
como leer y escribir datos.
7171

72-
.. seealso:: La conexión a una base de datos es una operación costosa, por lo
72+
.. seealso:: La conexión a una base de datos es una **operación costosa**, por lo
7373
que, si la aplicación abre y cierra constantemente conexiones, el rendimiento
7474
se penalizará. Para optimizarlo reaprovechando conexiones, existen los
7575
:ref:`pools de conexiones <conn-pool>`.
@@ -288,7 +288,7 @@ identificador se definió. Para ello podemos hacer lo siguiente:
288288
.. code-block:: java
289289
:emphasize-lines: 11, 20-22
290290
291-
TimeDateFormatter df = TimeDateFormatter.ofPattern("dd/MM/yyyy");
291+
DateTimeFormatter df = DateTimeFormatter.ofPattern("dd/MM/yyyy");
292292
// centro es un centro que ya tenemos definido.
293293
Profesor[] profesores = new Profesor[] {
294294
new Profesor ("Perico de los Palotes", LocalDate.parse("12/06/1995", df), centro),
@@ -357,14 +357,6 @@ a. El driver debe inferir el tipo |SQL| a partir del tipo de *Java*: eso
357357
358358
.. rubric:: Notas al pie
359359

360-
.. [#] Según el estándar, un nombre que no se encierra entre comillas dobles, se
361-
sobreentiende escrito todo en mayúsculas. En particular, :program:`SQLite` se
362-
salta el estándar en este aspecto y no hace esa distinción, incluso aunque
363-
los nombres sí se hayan entrecomillado. Otra razón que avala el que no
364-
entrecomillemos es que :program:`MariaDB`/:program:`MySQL` exige que
365-
cambiemos la configuración predeterminada para soportar el entrecomillado con
366-
comillas dobles, ya que este |SGBD| entrecomilla con el acento grave.
367-
368360
.. [#] En las versiones modernas de |JDBC| no es necesario expresar el nombre
369361
del driver, ya que dispone de mecanismos parar inferir cuál es el driver
370362
adecuado a partir de la |URL| de conexión. En cambio, más adelante, al
@@ -381,15 +373,27 @@ a. El driver debe inferir el tipo |SQL| a partir del tipo de *Java*: eso
381373
primarias en las tablas ``Claustro``, ``Departamento`` y ``Profesor``.
382374
Nuestra intención es que sus identificadores se generen automáticamente sin
383375
necesidad de que especifiquemos un valor. Eso se hace en SQLite_ con
384-
*AUTOINCREMENT*, pero en otros motores se expresa de otro modo, ya que no
385-
forma parte del estándar. Desde |SQL|\ :2003, existe un modo de expresarlo:
386-
:code:`GENERATED [BY DEFAULT|ALWAYS] AS IDENTITY`. Sin embargo, SQLite_ no lo
387-
soporta y por eso lo hemos dejado comentado. En cambio, tiene un comportamento
388-
curioso: cuando se usa ``INTEGER`` para definir la clave primaria asume el
389-
comportamento de :code:`GENERATED BY DEFAULT AS IDENTITY` y cuando se usa
390-
``INT`` la clave no se genera y hay que especificarla siempre explícitamente
391-
(o sea, el comportamiento que esperaríamos al no haber incluido ninguna
392-
fórmula adicional como :code:`AUTOINCREMENT` o :code:`GENERATED BY...`).
376+
*AUTOINCREMENT* (aunque es conveniente leer `las aclaraciones al respecto
377+
<https://sqlite.org/autoinc.html>`_), pero en otros motores se expresa de
378+
otro modo, ya que no forma parte del estándar. Desde |SQL|\ :2003, existe un
379+
modo de expresarlo: :code:`GENERATED [BY DEFAULT|ALWAYS] AS IDENTITY`: ambas
380+
fórmulas generan un identificar único automáticamente, pero la primera
381+
permite la inserción manual de valores, mientras que la segunda, no. Sin
382+
embargo, SQLite_ no lo soporta y por eso lo hemos dejado comentado. En
383+
cambio, tiene un comportamento curioso: cuando se usa ``INTEGER`` para
384+
definir la clave primaria asume el comportamento de :code:`GENERATED BY
385+
DEFAULT AS IDENTITY` y cuando se usa ``INT`` la clave no se genera y hay que
386+
especificarla siempre explícitamente (o sea, el comportamiento que
387+
esperaríamos al no haber incluido ninguna fórmula adicional como
388+
:code:`AUTOINCREMENT` o :code:`GENERATED BY...`).
389+
390+
.. [#] Según el estándar, un nombre que no se encierra entre comillas dobles, se
391+
sobreentiende escrito todo en mayúsculas. En particular, :program:`SQLite` se
392+
salta el estándar en este aspecto y no hace esa distinción, incluso aunque
393+
los nombres sí se hayan entrecomillado. Otra razón que avala el que no
394+
entrecomillemos es que :program:`MariaDB`/:program:`MySQL` exige que
395+
cambiemos la configuración predeterminada para soportar el entrecomillado con
396+
comillas dobles, ya que este |SGBD| entrecomilla con el acento grave.
393397
394398
.. |SQL| replace:: :abbr:`SQL (Structured Query Language)`
395399
.. |URL| replace:: :abbr:`URL (Uniform Resource Locator)`

0 commit comments

Comments
 (0)