Lurralde Plangintza, Etxebizitza eta Garraio Saila

API del buscador de euskadi.eus

El acceso a cualquier funcionalidad de la infraestructura de euskadi.eus está encapsulado en un API Java que permite:

  • Gestionar la taxonomía de etiquetas de catalogación común de Euskadi.eus.
  • Trabajar con contenidos y versiones idiomáticas: crear, borrar, modificar, aprobar, catalogar, etc.
  • Trabajar con portales o sitios web: sus páginas y áreas visuales.
  • Publicar contenidos, páginas de portal, etc.
  • Indexar en uno o varios motores de búsqueda cualquier contenido o página de portal.
  • Buscar contenidos o páginas de portal.

Los diferentes APIs (contenidos, portal, publicador, buscador, etiquetas de catalogación, etc.) abstraen al programador de la lógica de negocio implementada en los núcleos de la infraestructura que es donde realmente reside la inteligencia del sistema.

De todos los APIs disponibles, el API del Buscador es uno de los más interesantes de cara a la reutilización ya que facilita la localización de contenidos y sus meta-datos para su procesado; este API tiene dos sabores:

API pseudo-REST

El API pseudo-REST permite crear consultas de búsqueda codificadas en una URL del navegador que se lanzan contra el núcleo de búsqueda de euskadi.eus y devuelve el resultado en XML.

API Java

El API Java facilita la creación de queries y el tratamiento de los resultados devueltos frente al API pseudo-REST.

1.Crear la Query

R01MSearchQuery qry = R01MSearchQuery.create()
    .typedInAnyOfTheeseClusters(R01MTypoClusterOID.forId("euskadi"))     
    .typedInAnyOfTheeseFamilies(R01MTypoFamilyOID.forId("procedimientos_administrativos"))
    .typedInAnyOfTheeseTypes(R01MTypoTypeOID.forId("ayuda_subvencion"))
    .mustMeetTheeseMetaDataConditions(R01MSearchQueryNumberMetaData
                                           .forMetaData("procedureStatus")
                                           .usingCondition(R01MSearchQueryNumberMetaDataCondition.EQUALS)
                                           .with(16))
    .mustHaveStructureLabel("label1");

2.Crear la sesión de búsqueda

Es preciso crear una sesión de búsqueda para conseguir los resultados del servicio: cuántos resultados, paginación, número de elementos:

R01MSearchSession session = R01MSearchSession.forQuery(qry);

R01MSearchResultItem[] items = session.getCurrentPageSearchResults()

StringBuffer dbg = new StringBuffer();

    if (items != null) {

           for (R01MSearchResultItem item : items) {

            dbg.append("-").append(item.getContentName())

                 .append(": ").append(item.getDocumentName()).append("\r\n");

           }

    }

Ejemplo completo

Los pasos a seguir son los siguientes:

  1. Componer la query para filtrar los contenidos deseados
  2. Obtener una sesión de búsqueda (ejecutar la query)
  3. Iterar sobre los ítems de resultados
  4. Obtener meta-datos devueltos por el buscador:
    • Meta-datos comunes como el nombre del contenido, la versión idiomática, fechas de publicación, etc
    • Meta-datos específicos del tipo de contenido indexados en el motor de búsqueda

Ejemplo: de una ayuda se indexan algunos meta-datos buscables como el estado de vigencia; estos metadatos son devueltos en los resultados de búsqueda. Dado que el buscador NO tiene todos los meta-datos del tipo de contenido es necesario acceder al XML que contiene los meta-datos específicos de cada tipo de contenido.

NOTA: En los documentos publicados en opendata.euskadi.eus se puede tener una visión de la estructura de los contenidos de Euskadi.eus:

Para acceder al XML de los meta-datos específicos dependientes del tipo de contenido hay dos opciones:

  1. Acceder directamente al XML que contiene los metadatos vía HTTP
  2. Descargar un fichero ZIP con el contenido y recuperar el XML del ZIP

En cualquiera de los dos casos es importante conocer la estructura de directorios de los contenidos de Euskadi.eus; que en grandes líneas es el siguiente:

[contenido]

|- [contentName]-content.xml
|- [contentName]-idxContent.xml
|- [lang]_[version]
|_[data]

|- [dataFile] <<< Este fichero es el que interesa ya que contiene los meta-datos específicos del tipo de contenido

Por lo tanto, para cada resultado habrá que:

  1. Obtener la carpeta que contiene los datos de la versión idiomática
  2. Obtener el nombre del fichero que contiene los datos específicos
  3. Descargarse el fichero con los datos específicos
  4. Parsear el ficheoro XML para acceder a los datos

Un ejemplo de la secuencia de pasos anterior es la siguiente:

/**
* Construir una query para buscar las ayudas de Euskadi.eus
* @param items los items a procesar
* @return
*/
private static R01MSearchQuery _buildQuery() {public static void main(String[] args) {
   try {
       // Construir la query
       R01MSearchQuery qry = _buildQuery();
       
       // Inicializar la sesioacute;n
       R01MSearchSession session = R01MSearchSession.forQuery(qry);
       
       System.out.println(session.queryAsURL());
       
       // Un poco de debug
       String dbg = Strings.create()
                           .add("[Resultados de la bsqueda: \r\n")
                           .add("\t- query como XML: ").addLine(session.queryAsXML())
                           .add("\t- query codificada en la URL: ").addLine(session.queryAsURL())
                           .add("\t- Resumen de la ejecución{} resultados en {} páginas de {} items (max) cada una")
                                   .customizeWith(session.getPager().getTotalNumberOfItems(),
                                                  session.getPager().getPageCount(),
                                                  session.getPager().getPageSize())
                           .asString();
       System.out.println(dbg);
       
       // Recoger resultados
       R01MSearchResultItem[] resultItems = session.getCurrentPageSearchResults();
       System.out.println(_processResultItems(resultItems));
       
       // Ir a la siguiente p´gina de resultados
       resultItems = session.goToNextPage();
       System.out.println(_processResultItems(resultItems));
       
       // ... mas...
       
    } catch (Exception ex) {
       ex.printStackTrace(System.out);
    }
}

    R01MSearchQuery qry = R01MSearchQuery.create()
                                    .typedInAnyOfTheeseClusters(R01MTypoClusterOID.forId("euskadi"))
                                    .typedInAnyOfTheeseFamilies(R01MTypoFamilyOID.forId("procedimientos_administrativos"))
                                    .typedInAnyOfTheeseTypes(R01MTypoTypeOID.forId("ayuda_subvencion"))
                                    .mustMeetTheeseMetaDataConditions(R01MSearchQueryNumberMetaData
                                                                             .forMetaData("procedureStatus")
                                                                             .usingCondition(EQUALS)
                                                                             .with(16));

    return qry;
}

 

/**
* Procesar los resultados de bsqueda: acceder a algunos datos:
*      - MetaDatos comunes como el nombre del contenido y versión idiomáca
*      - MetaDatos específicos: XML del tipo de contenido
* @param items los items a procesar
* @return
*/
private static String _processResultItems(final R01MSearchResultItem[] items) {
    StringBuffer dbg = new StringBuffer();

    if (items != null) {

       for (R01MSearchResultItem item : items) {
           ItemData itemData = new ItemData();
       
           // Algunos metaDatos comunes
           // -------------------------
           itemData.setContentName(item.getContentName());
           itemData.setLang(item.getDocumentLanguage());
           itemData.setLangVersionName(item.getDocumentName());
           
            // Algunos meta-datos relevantes dependientes del tipo de contenido
           // pero que son devueltos por el buscador
           // ----------------------------------------------------------------
           Map indexedMD = item.getDocumentMetaData();
           itemData.setProcedureStatus(indexedMD != null ? indexedMD.get("procedureStatus").toString() : null);
           

           // MetaDatos específicos disponibles en un XML que hay que descargar de Euskadi.eus
           // --------------------------------------------------------------------------------
           // Buscar el datafile (esta parte NO estaba pensada para ser utilizad en OpenData, pero
           // es prácticamente la única opción que hay WTF!)
           // item.getDocumentDataFilesGeneratedFilesDocumentRelativePaths() es un Mapa
           // que relaciona los oids de los datafiles (que es lo que se necesita) con el fichero HTML
           // generado
           // Ej:  
           //          procedimiento_ayuda_v2;main:abandono_2010.html 
           //          
           //      
           // (se necesita el oid: r01dpd013a780b8c401e41497a285409dc514c34e)
           Map dataFiles = item.getDocumentDataFilesGeneratedFilesDocumentRelativePaths();
           String dataFileOid = !CollectionUtils.isNullOrEmpty(dataFiles) ?
                                         CollectionUtils.of(dataFiles.keySet()).pickOneElement()
                                           :
                                         null;
           
           String dataFileXML = null;
           if (dataFileOid != null) {

               try {

                   String dataFileURL = "http://www.euskadi.eus/contenidos/" +

                                              item.getDocumentWorkAreaRelativePath() +

                                              "/data/" + item.getDocumentLanguage() + "_" + dataFileOid;

                   dataFileXML = HttpClient.forUrl(dataFileURL)

                                           .withoutParameters()

                                           .usingGETRequestMethod()

                                           .loadAsString();

                  dataFileXML = dataFileXML.trim();

               } catch (Exception ex) {

                   ex.printStackTrace(System.out);

               }

           }
           itemData.setDataFileXML(dataFileXML);
           
           // Assets reusables (ZIP con el contenido)
           // ---------------------------------------
           Map rispAssets = item.getContentRispDocumentsInfo();
           if (!CollectionUtils.isNullOrEmpty(rispAssets)) {
               Map assets = new HashMap(rispAssets.size());
               for (Map.Entry assetInfo : rispAssets.entrySet()) {
                   Path assetPath = Path.of(assetInfo.getValue().split(",")[0]);
                   assets.put(assetInfo.getKey(),assetPath);
               }
               itemData.setReusableAssets(assets);
           }
           // Debug:
           System.out.println("\r\n\r\n\r\n_________________________________________________\r\n" +
                              itemData.debugInfo());
       }
    }
    return dbg.toString();
}

 

@Accessors(prefix="_")
static class ItemData implements Debuggable {
    @Getter @Setter private String _contentName;
    @Getter @Setter private String _lang;
    @Getter @Setter private String _langVersionName;
    @Getter @Setter private String _procedureStatus;
    @Getter @Setter private Map _reusableAssets;
    @Getter @Setter private String _dataFileXML;
    
    @Override
    public String debugInfo() {
       return Strings.create().add("     Content Name: ").addLine(_contentName)
                              .add("Lang Version Name: ").add("(").add(_lang).add(")").addLine(_langVersionName)
                              .add(" Procedure status: ").addLine(_procedureStatus)
                              .add("  Reusable Assets: ").addLine((_reusableAssets != null ? 
                                                                                _reusableAssets.values().toString()
                                                                                :
                                                                                ""))
                              .add("     DataFile XML: ").add(_dataFileXML)
                              .asString();
    }
}

 

Como se puede ver la obtención de datos de opendata.euskadi.eus es relativamente sencilla, aunque aún tiene cierta complejidad ya que el programador ha de:

  • Ser consciente de la estructura de directorios de euskadi.eus y dónde se encuentran los meta-datos específicos del tipo de contenido (tipos de contenido y metadatos de euskadi.eus).
  • Parsear el fichero XML de meta-datos específicos