2011-06-10 18 views
5

Estoy tratando de escribir un panel de control de seguridad que muestra el estado de la copia de seguridad de múltiples servidores. La idea es mostrar una tabla con JSP que tenga las fechas de los últimos días en las columnas y los nombres de los servidores en filas. En la mesa de este pobre, escribí los valores Sí/No.Iterar un Multimap con JSP

+------------+------------+------------+------------+ 
+ Host Name | 2011-06-10 | 2011-06-09 | 2011-06-08 | 
+------------+------------+------------+------------+ 
| web01  |  Y  |  Y  |  N  | 
+------------+------------+------------+------------+ 
| web02  |  Y  |  Y  |  Y  | 
+------------+------------+------------+------------+ 

Cada servidor, hace su propia copia de seguridad y guarda el estado en Amazon SimpleDB y yo escribimos un método Java para recuperar esta información de los últimos días con la firma siguiente:

/** 
* List MySQL backups of the last howManyDays days. It starts from today 
* included at index 0 and goes back in the past until we have a list of 
* howManyDays days, even if some day doesn't have any data. Return a list of 
* dates, each of which contains a list of backup jobs executed by servers in 
* that day. 
* 
* @param howManyDays 
*   how many days of backup to show 
* @return a Map where each key is the date in ISO format (2011-06-10) and each 
*   element is a backupJob which is represented by a Map where the key is 
*   the server name (ex. web01, web01) and the value is "Y" if all was 
*   fine, otherwise it contains the error message. 
*/ 
public Multimap<String, Map<String, String>> listMysqlBackups(int howManyDays); 

Multimapa es Google Guava Multimap porque tengo varias copias de seguridad por día. Ejemplo de salida:

{2011-06-10=[{web06=Y}, {web05=Y}], 2011-06-08=[{web05=Y}, {web06=Y}], 
2011-06-09=[{web05=Y}, {web06=Y}], 2011-06-07=[{web05=Y}, {web06=Y}]} 

No sé cómo consumir esta información en JSP. Probé con foreach:

<c:forEach items="${backups}" var="backup" varStatus="backupId"> 
    ${backup.key} 
</c:forEach> 

y la respuesta fue:

javax.servlet.ServletException: javax.servlet.jsp.JspTagException: Don't know 
how to iterate over supplied "items" in <forEach> 

Ahora estoy pensando si estoy pegarme un tiro en el pie con un valor de retorno demasiado complejo y si debo lugar de retorno un ArrayList simple de HashMap donde cada HashMap contiene toda la información necesaria (fecha, nombre de host, mensaje). Si piensan que es un mejor enfoque, no tengo ningún problema para volver a escribir el método Java extrayendo los datos, pero ahora cada celda requerirá recorrer todo ArrayList para obtener el elemento (lo cual podría estar bien porque 6 servidores por 7 los días son solo 42 elementos).

¿Cómo abordaría este problema?

Respuesta

8

La etiqueta JSTL forEach no soporta Multimaps. Solo puede iterar sobre colecciones/mapas/matrices estándar.

Cuando necesito iterar sobre un Multimap en un JSP, utilizo su vista asMap(). Esto me permite usar forEach, ya que sabe cómo iterar sobre la interfaz Map.

Se vería como el siguiente:

public Multimap<String, Map<String, String>> listMysqlBackups(int howManyDays) { 
    // ... 
} 

public Map<String, Collection<Map<String, String>>> getListMysqlBackupsAsMap() { 
    return listMysqlBackups(this.numberOfDays).asMap(); 
} 


<c:forEach var="backup" items="${bean.listMysqlBackupsAsMap}"> 
    <c:set var="dateISO" value="${backup.key}/> 
    <c:set var="backupJobs" value="${backup.value}/> <!-- a Collection<Map<String,String>> --> 
    <c:forEach var="backupJob" items="${backupJobs}"> 
     <!-- do something with each backup job (Map<String, String>) for the current date --> 
    </c:forEach> 
</c:forEach> 

Si puede utilizar JSP EL 2.1, no es necesario el captador adicional. Simplemente puede llamar al asMap() dentro del JSP para obtener la vista Map.


Dicho todo esto, no estoy seguro de que su uso de un Multimap realmente hace lo que usted quiere aquí. Un Multimap<String, Map<String, String>> asigna cada clave a una colección de Map<String,String>. En su caso, esto significa que usted tiene:

2011-06-09 
    --> Collection 
     --> Map<String, String> 
     --> Map<String, String> 
     --> Map<String, String> 
2011-06-10 
    --> Collection 
     --> Map<String, String> 
     --> Map<String, String> 
     --> Map<String, String> 

No estoy seguro de que eso es lo que usted quiere aquí. Creo que quieres un Map<String, Map<String, String>>.

Otra solución sería utilizar Guava Table.

+1

+1 para usar Table para representar una tabla. – Ray

+0

Si utilizo las claves como dije date + hostname (ni siquiera necesito el guión bajo), puedo bajar para tener un simple Map y ya es suficiente, y no necesito bucle para obtener elementos. La tabla también parece interesante, pero ¿funciona JSP forEach en ella? De lo contrario, agregará complejidad, mientras que el propósito es reducir la complejidad. – stivlo

+0

Acepto su respuesta para la explicación útil y detallada, aunque decidí usar mi solución explicada a continuación porque tener solo un HashMap es más simple en lugar de tener que atravesar tres niveles. Gracias por tu ayuda. – stivlo

-1

Creo que se debe tratar de una especie de bucle anidado para

por ejemplo,

<c:forEach items="${webs}" var="web" varStatus="webId"> 
    <c:forEach items="${web.backups}" var="backup" varStatus="backupId"> 
     ${backup.key} 
    </c:forEach> 
</c:forEach> 
+0

Bueno, sí, pero el problema es que ni siquiera puedo codificar el bucle externo en el Multimap, porque JSP forEach no sabe cómo iterarlo, como en el mensaje de error que se muestra arriba. – stivlo

+0

Puede probar cualquier otro mapa como 'HashMap' en lugar de' MultiMap' –

+0

No con la siguiente estructura, ya que tengo varios valores para cada clave, entonces o bien cambio la estructura de datos, por ejemplo con una ArrayList como expliqué, o no puedo usar HashMap o sobrescribiría los valores de la clave. O podría usar un HashMap con una fecha clave combinada + guión bajo + nombre de host. – stivlo

1

Solo para resumir lo que he hecho, y sin afirmar ser la mejor solución, respondo a mi pregunta. Me interesa saber si el uso de la colección de Google Table podría hacer las cosas más simples o no.

Cambié el tipo de devolución de listMysqlBackups a un HashMap simple.

/** 
* List the MySQL backups of the last howManyDays days. It starts from today and 
* goes back in the past until we have a list of howManyDays days, even if some 
* day doesn't have any data. Return a Map with each index as the ISO date 
* underscore the server name. Key example: 2011-06-11_web01 
* 
* @param howManyDays 
*   how many days of backup to show 
* @return a Map where each key is the date in ISO format and each element is 
*   a backupJob which is represented by a Map where the key is the server 
*   name (ex. web01, web01) and the value is "Y" if all was fine, 
*   otherwise it contains the error message. 
*/ 
public Map<String, String> listMysqlBackups(int howManyDays) 

han añadido nuevos métodos para devolver la lista día y la lista de servidores.

public static List<String> listDatesFromToday(int howManyDays) { 
    List<String> dates = new ArrayList<String>(); 
    String currentDay = DateHelper.getCurrentDateAsIso(); 
    while (howManyDays > dates.size()) { 
     dates.add(currentDay); 
     currentDay = DateHelper.previousDay(currentDay); 
    } 
    return dates; 
} 

public static List<String> listHosts() { 
    return ImmutableList.of("web05", "web06"); 
} 

muestran la tabla con un bucle anidado. Puedo buscar las claves directamente sin buscarlas en el mapa, debido a la forma en que construí la clave.

<table class="dataTable"> 
    <tr> 
    <th></th> 
    <c:forEach items="${days}" var="day"> 
    <th>${day}${host}</th> 
    </c:forEach> 
    </tr> 
<c:forEach items="${hosts}" var="host"> 
    <tr> 
    <th>${host}</th> 
    <c:forEach items="${days}" var="day"> 
    <c:set var="key" value="${day}_${host}"/> 
    <td> ${backups[key]} </td> 
    </c:forEach> 
    </tr> 
</c:forEach> 
</table> 

creo que esta solución es simple, y estoy contento con él, pero si ustedes piensan que la tabla recogida Google hace más simple, el código más corto y más limpio que estaría feliz de escuchar.