Al terminar este módulo serás capaz de:
TaxonomyExtension que añada categorías, usos de datos e interesados;refineCategory, añadir atributos y declarar obsoletos tus propios
términos;ServiceLoader, en el class path o en el module path;La taxonomía estándar cubre mucho, pero cada organización tiene sus propios datos. La universidad necesita:
user.education.* encaja;HIGH), y no grave (MEDIUM), revelar números de identificación nacional;Nada de esto exige cambiar Linneo. Una TaxonomyExtension lo aporta a la taxonomía al construirla.
Antes de extender, revisa la taxonomía estándar: una clave existente que sea cierta para el dato siempre es mejor que una nueva, porque otras herramientas y organizaciones ya la entienden. En una universidad, revisa también la extensión universitaria que incluye Linneo (ver más abajo): ya tiene claves para tesis, asistencia, expedientes disciplinarios y mucho más.
package edu.example.university;
import io.github.devbpm.linneo.Categories;
import io.github.devbpm.linneo.Confidentiality;
import io.github.devbpm.linneo.TaxonomyBuilder;
import io.github.devbpm.linneo.TaxonomyExtension;
public final class CampusTaxonomy implements TaxonomyExtension {
@Override
public String name() {
return "campus";
}
@Override
public void contribute(TaxonomyBuilder builder) {
builder.category("user.education.lab_notebook", "Cuaderno de laboratorio",
"Cuadernos de laboratorio del interesado: experimentos, mediciones y resultados.",
c -> c.attribute("retention", "permanent"))
.category("user.education.lab_journal", "Diario de laboratorio",
"Nombre anterior de la clave del cuaderno de laboratorio.",
c -> c.deprecated("2.0", "user.education.lab_notebook"))
.refineCategory(Categories.USER_GOVERNMENT_ID, c -> c.confidentiality(Confidentiality.HIGH))
.dataSubject("visiting_researcher", "Investigador visitante", "Investigador de otra institución.");
}
}
name()Un nombre corto y estable. Se registra como origen de todos los términos que añade la extensión, de modo que los
informes distinguen los términos de «campus» de los de «fideslang» y «linneo». Esos dos nombres están
reservados: una extensión llamada "fideslang" o "linneo" (o con el nombre vacío) se rechaza con
Invalid extension name 'linneo' in edu.example.university.CampusTaxonomy. Aplicar dos veces la misma clase de extensión
no tiene efecto la segunda vez, pero dos clases distintas con el mismo nombre se rechazan (véase más abajo). El nombre "university" lo usa la
extensión incluida en Linneo; no lo reutilices.
category(key, name, description) añade una categoría que hereda toda su protección de su madre.
category(key, name, description, customizer) permite declarar cosas en un CategoryDraft:
Método de CategoryDraft |
Declara |
|---|---|
nature(LegalNature) |
la naturaleza jurídica; debe admitirla la de la madre (LegalNature.admits) |
confidentiality(Confidentiality) |
la confidencialidad; nunca inferior a la de la madre |
integrity(Integrity) |
la integridad; nunca inferior a la de la madre |
secret() |
marca la categoría, y por tanto todas sus hijas, como secreta |
attribute(name, value) |
un atributo definido por la aplicación, que heredan las hijas que no fijan el suyo |
since(version) |
la versión de tu extensión que introdujo el término |
deprecated(sinceVersion, replacedBy) |
declara obsoleto el término; replacedBy puede ser null |
Con esta extensión, user.education.lab_notebook queda así:
nature=ORDINARY, confidentiality=MEDIUM, integrity=HIGH, secret=false,
attributes={retention=permanent}, origin=campus
Los niveles vienen de user.education; el atributo es de la extensión. Cualquier hija que añadas más adelante bajo
user.education.lab_notebook hereda retention=permanent salvo que fije su propio valor. En el inventario del módulo 5
puedes imprimirlo con field.category().attribute("retention").
La clave debe estar formada por segmentos en minúsculas separados por puntos ([a-z][a-z0-9_]*) y su madre debe
existir. Añade los términos como hijos de términos existentes: así resultan comprensibles para cualquiera que
conozca el árbol estándar.
refineCategory(key, customizer) modifica una categoría que ya está en el builder: estándar o añadida por una
extensión anterior. Puede subir niveles, convertir una naturaleza ORDINARY en otra más estricta, marcarla como
secreta, añadir atributos o declararla obsoleta. Nunca puede debilitarla:
.refineCategory(Categories.USER_GOVERNMENT_ID, c -> c.confidentiality(Confidentiality.HIGH))
A partir de aquí, user.government_id.passport_number, ...national_identification_number,
...social_security_number y todas las demás hijas heredan HIGH. Refinar no cambia el origen del término:
user.government_id sigue siendo fideslang.
dataUse(key, name, description) y dataSubject(key, name, description) (cada uno con un personalizador
TermDraft opcional para since y deprecated) añaden términos a las otras dos jerarquías. No tienen facetas de
protección.
Supón que la universidad usó user.education.lab_journal en una primera versión y después lo renombró a
user.education.lab_notebook. Nunca borres ni reutilices una clave que las aplicaciones puedan haber guardado; declárala
obsoleta:
.category("user.education.lab_journal", "Diario de laboratorio",
"Nombre anterior de la clave del cuaderno de laboratorio.",
c -> c.deprecated("2.0", "user.education.lab_notebook"))
Los campos que siguen anotados con la clave antigua continúan funcionando, se clasifican con
user.education.lab_notebook y producen el aviso que viste en el módulo 4:
WARNING DEPRECATED_CATEGORY edu.example.university.LabNotebook.file: data category 'user.education.lab_journal' is deprecated; use 'user.education.lab_notebook'
Categories solo cubre la taxonomía estándar. Da también constantes de compilación a tus claves, para que funcionen
en las anotaciones y las erratas no compilen:
public final class CampusCategories {
public static final String USER_EDUCATION_LAB_NOTEBOOK = "user.education.lab_notebook";
private CampusCategories() {
}
}
@Category(CampusCategories.USER_EDUCATION_LAB_NOTEBOOK)
private String notebookTitle;
Taxonomy.load() aplica todas las extensiones que encuentra java.util.ServiceLoader. Registra la tuya de una de
estas dos formas.
Class path: un fichero llamado META-INF/services/io.github.devbpm.linneo.TaxonomyExtension (en Maven, dentro de
src/main/resources) con el nombre completo de la clase:
edu.example.university.CampusTaxonomy
Module path: una cláusula provides en tu module-info.java:
module edu.example.university {
requires io.github.devbpm.linneo;
provides io.github.devbpm.linneo.TaxonomyExtension with edu.example.university.CampusTaxonomy;
}
Después, en todas partes (aplicación y tests):
Taxonomy taxonomy = Taxonomy.load(); // estándar + todas las extensiones registradas
load() busca las extensiones con el class loader de contexto del hilo (o con el de Linneo si no lo hay) y las aplica
ordenadas por nombre, de modo que el resultado no depende del orden del class path. Si necesitas otro class
loader, usa Taxonomy.builder().withStandard().withExtensions(classLoader).build().
También puedes aplicar una extensión de forma explícita, sin ServiceLoader, algo práctico en un test unitario de la
propia extensión:
Taxonomy taxonomy = Taxonomy.builder().withStandard().extend(new CampusTaxonomy()).build();
Linneo incluye una extensión opcional para la educación superior, UniversityTaxonomy, en el paquete
io.github.devbpm.linneo.extension.university. Añade 33 categorías de datos (asistencia, situación académica,
adaptaciones, solicitudes de admisión y su evaluación, notas finales, reconocimiento de créditos, títulos, prácticas,
valoraciones de renta, exenciones de tasas, donaciones…), 27 usos de datos (entre ellos dos raíces nuevas,
advancement y research) y 13 interesados (futuro estudiante, doctorando, estudiante de intercambio, donante, autor
de cartas de referencia…). La referencia enumera todos los
términos con su protección.
No está registrada en ServiceLoader, así que Taxonomy.load() nunca la aplica. Aplícala junto a tu propia
extensión:
Taxonomy taxonomy = Taxonomy.builder()
.withStandard()
.extend(new UniversityTaxonomy()) // io.github.devbpm.linneo.extension.university
.extend(new CampusTaxonomy())
.build();
taxonomy.toString() imprime Taxonomy[160 data categories, 90 data uses, 35 data subjects]: las 125 categorías
estándar, 33 de university y 2 de campus. Sus claves tienen constantes en UniversityCategories,
UniversityDataUses y UniversityDataSubjects, así que funcionan en las anotaciones igual que las estándar:
@Category(UniversityCategories.USER_EDUCATION_ATTENDANCE)
private int absences;
Dos extensiones se combinan sin problemas cuando:
university, campus) y la identidad de
la extensión: extend lanza una TaxonomyException cuando llega una clase distinta con el nombre de una ya
aplicada: The extensions io.github.devbpm.linneo.extension.university.UniversityTaxonomy and
edu.example.university.CampusTaxonomy are both named 'university'. (Aplicar dos veces exactamente la misma clase es
inocuo, y por eso una extensión puede aplicarse de forma explícita y estar además registrada como servicio.)Duplicate key 'user.education.thesis'. Por eso CampusTaxonomy añade cuadernos de laboratorio y no tesis.UniversityTaxonomy si se aplica después; si no, refineCategory falla con
Cannot refine unknown data category 'user.education.attendance'. Con extend eliges tú el orden. load() ordena
por nombre, así que para aplicar primero la extensión incluida y después todas las registradas, escribe
Taxonomy.builder().withStandard().extend(new UniversityTaxonomy()).withExtensions().build().Para que Taxonomy.load() incluya la extensión de Linneo, regístrala en tu aplicación; la referencia de
Extensiones muestra cómo en el class path y en
el module path.
build()El builder es donde se aplican las reglas del módulo 2. Algunos problemas se detectan en cuanto llamas a un método del
builder y el resto al construir la taxonomía; en cualquier caso, Taxonomy.load() lanza una TaxonomyException, de
modo que una extensión defectuosa detiene la aplicación (o el test) en lugar de producir una taxonomía más débil. Los
mensajes son:
| Problema | Mensaje de ejemplo | Cuándo |
|---|---|---|
| Clave no válida | Invalid key 'user.education.LabNotebook': use lower-case segments separated by dots |
en category(...) |
| Clave duplicada | Duplicate key 'user.education.grade' |
en category(...) |
| Refinar una clave que (aún) no existe | Cannot refine unknown data category 'user.education.lab_notebook' |
en refineCategory(...) |
| El refinamiento baja un nivel declarado por la categoría | Cannot lower the confidentiality of 'user.education' from MEDIUM to LOW |
en refineCategory(...) |
| El refinamiento cambia la naturaleza a una no admitida | Cannot change the nature of 'user.health_and_medical' from SPECIAL_CATEGORY to ORDINARY |
en refineCategory(...) |
| Falta la madre | The parent 'user.education.lab_notebook' of data category 'user.education.lab_notebook.entry' does not exist |
build() |
| Raíz sin todas las facetas | Root data category 'research' must declare its nature, confidentiality and integrity |
build() |
| Naturaleza no admitida por la madre | Data category 'user.education.stats' declares nature NONE, which its parent 'user.education' (ORDINARY) does not admit |
build() |
| Nivel inferior al de la madre | Data category 'user.education.grade' declares confidentiality LOW, lower than its parent's MEDIUM |
build() |
| La sustituta no existe | The replacement 'user.education.new' of data category 'user.education.old' does not exist |
build() |
| La sustituta protege menos | The replacement 'user.education.new' of data category 'user.education.old' has weaker protection: ORDINARY, LOW, LOW instead of ORDINARY, MEDIUM, HIGH |
build() |
| Las sustituciones forman un ciclo | The replacements of data category 'user.education.a' form a cycle |
build() |
Dos casos merecen atención:
user.education.grade no declara confidencialidad propia, así
que refineCategory(USER_EDUCATION_GRADE, c -> c.confidentiality(LOW)) lo acepta refineCategory, pero después
build() la compara con la madre y falla («declares confidentiality LOW, lower than its parent’s MEDIUM»). De un
modo u otro, no se puede bajar.refineCategory(USER_DEMOGRAPHIC, c ->
c.confidentiality(HIGH)) falla al construir con Data category 'user.demographic.date_of_birth' declares
confidentiality MEDIUM, lower than its parent's HIGH, porque varias hijas de user.demographic declaran MEDIUM
de forma explícita. Refina también esas hijas, o refina solo las claves concretas que te interesan.El orden importa en refineCategory: la clave ya debe estar en el builder. Los términos estándar siempre lo están; los
de otra extensión solo si el nombre de esa extensión va antes que el tuyo en orden alfabético.
UNKNOWN_CATEGORY o, peor aún, usa Taxonomy.standard() y nunca ve los niveles más estrictos.Taxonomy.standard() donde se necesite la extensión: nunca incluye extensiones."linneo" o "fideslang" como nombre de la extensión, o "university", que usa la extensión incluida.Ejercicio 6.1 — Préstamos de biblioteca. Añade user.education.library_loan («Préstamo de biblioteca», «Libros y
otros materiales que el interesado ha tomado en préstamo de la biblioteca.») a CampusTaxonomy sin declarar ninguna
faceta. ¿Cuáles son su naturaleza, confidencialidad,
integridad y origen efectivos? Compruébalo con requireCategory.
Ejercicio 6.2 — Direcciones postales. La universidad decide que las direcciones postales de estudiantes y
personal tienen confidencialidad MEDIUM. Añade el refinamiento. ¿Funcionará build()? ¿Qué claves cambian?
Ejercicio 6.3 — Predice. Para cada cuerpo de extensión, predice si
Taxonomy.builder().withStandard().extend(...).build() funciona y, si no, el mensaje:
builder.category("user.education.lab_notebook.entry", "Entrada", "Una entrada de un cuaderno de laboratorio."); (solo)builder.category("user.education.special_needs", "Necesidades especiales", "Adaptaciones para estudiantes con discapacidad.", c -> c.nature(LegalNature.SPECIAL_CATEGORY));builder.refineCategory(Categories.USER_HEALTH_AND_MEDICAL, c -> c.nature(LegalNature.ORDINARY));builder.refineCategory(Categories.USER_EDUCATION_TEST_SCORE, CategoryDraft::secret);Ejercicio 6.4 — Registrar y probar. Registra CampusTaxonomy con ServiceLoader y escribe un test que
compruebe que Taxonomy.load() contiene user.education.lab_notebook con origen "campus" y que
user.government_id.passport_number tiene confidencialidad HIGH.
Ejercicio 6.5 — Combinar. Construye una taxonomía con los términos estándar, la UniversityTaxonomy incluida y
después CampusTaxonomy.
toString()?user.education.thesis, y el de user.education.lab_notebook?CampusTaxonomy también añade user.education.thesis?CampusTaxonomy.name() devolviera "university"?refineCategory el origen de un término?Taxonomy.load() las extensiones?PUBLIC la clave user.contact.email?ServiceLoader en el class path?Taxonomy.load() la extensión universitaria que trae Linneo?← Anterior: Módulo 5 — Usar la clasificación · Siguiente: Módulo 7 — OpenMetadata →
Ejercicio 6.1
builder.category("user.education.library_loan", "Préstamo de biblioteca",
"Libros y otros materiales que el interesado ha tomado en préstamo de la biblioteca.");
Efectivos: ORDINARY, MEDIUM, HIGH (de user.education), no secreta, origen "campus".
Ejercicio 6.2
.refineCategory(Categories.USER_CONTACT_ADDRESS, c -> c.confidentiality(Confidentiality.MEDIUM))
Funciona: user.contact.address y sus hijas (city, country, postal_code, state, street) no declaran
confidencialidad, así que todas heredan MEDIUM. user.contact.email y las demás claves de contacto no cambian.
Ejercicio 6.3
The parent 'user.education.lab_notebook' of data category
'user.education.lab_notebook.entry' does not exist (salvo que también se añada user.education.lab_notebook).ORDINARY admite SPECIAL_CATEGORY, pero los datos de categoría especial deben tener la
máxima protección y la clave hereda confidencialidad MEDIUM de user.education: Data category
'user.education.special_needs' is SPECIAL_CATEGORY data and must have HIGH confidentiality and integrity, not
MEDIUM and HIGH. Declarar c.nature(LegalNature.SPECIAL_CATEGORY).confidentiality(Confidentiality.HIGH) hace que
funcione.refineCategory: Cannot change the nature of 'user.health_and_medical' from SPECIAL_CATEGORY to
ORDINARY.Ejercicio 6.4
src/main/resources/META-INF/services/io.github.devbpm.linneo.TaxonomyExtension:
edu.example.university.CampusTaxonomy
(o provides io.github.devbpm.linneo.TaxonomyExtension with edu.example.university.CampusTaxonomy; en
module-info.java), y:
@Test
void campusExtensionIsLoaded() {
Taxonomy taxonomy = Taxonomy.load();
assertEquals("campus", taxonomy.requireCategory("user.education.lab_notebook").info().origin());
assertEquals(Confidentiality.HIGH,
taxonomy.requireCategory(Categories.USER_GOVERNMENT_ID_PASSPORT_NUMBER).confidentiality());
}
Ejercicio 6.5
Taxonomy taxonomy = Taxonomy.builder()
.withStandard()
.extend(new UniversityTaxonomy())
.extend(new CampusTaxonomy())
.build();
Taxonomy[160 data categories, 90 data uses, 35 data subjects]: 125 + 33 + 2 categorías, 63 + 27 usos y
21 + 13 + 1 interesados. Aplicar las dos extensiones en el orden contrario da la misma taxonomía.university y campus.extend(new CampusTaxonomy()) lanza TaxonomyException con Duplicate key 'user.education.thesis'.extend(new CampusTaxonomy()) lanza TaxonomyException con The extensions
io.github.devbpm.linneo.extension.university.UniversityTaxonomy and edu.example.university.CampusTaxonomy are both
named 'university': dos clases distintas no pueden compartir nombre, y CampusTaxonomy.contribute no llega a
ejecutarse.Cuestionario
name() de la extensión.user.contact.email hereda LOW de user; PUBLIC es inferior, así que build() falla.META-INF/services/io.github.devbpm.linneo.TaxonomyExtension (en Maven, dentro de src/main/resources).extend(new UniversityTaxonomy()) o regístrala en tu aplicación.