一、查询用户最近的通话信息
--------------------------------------------
1.实现分析
使用ssm可视界面提供查询串 -- controller连接 hiveserver2 -- 将命令转化成hsql语句 -- hive绑定hbase,进行MR聚合查询
2.启动yarn集群 以及其高可用
a.$s100> start-yarn.sh
b.$s500> yarn-daemon.sh start resourcemanager
c.验证web界面:http://s100:8088
3.初始化hive
a.在mysql中创建存放hive信息的数据库
mysql > create database hive;
b.初始化hive的元数据(表结构)到mysql中
$> cd /soft/hive/bin
$> schematool -dbType mysql -initSchema
c.进入hive shell
$> hive
d.创建mydb数据库
$hive> create database mydb;
e.创建外部表,映射到hbase数据库
$hive> create external table ext_calllogs_in_hbase (id string, caller string,callTime string,callee string,callDuration string)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,f1:caller,f1:callTime,f1:callee,f1:callDuration")
TBLPROPERTIES ("hbase.table.name" = "call:calllogs");
f.hive查询17731086666的最近的10条通话记录,并按照时间降序排列
$hive> select * from ext_calllogs_in_hbase where id like '%17731086666%' order by callTime desc limit 10;
4.编程实现hive查询
a.添加依赖
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-jdbc</artifactId>
<version>1.2.1</version>
</dependency>
b.ssm中创建service,查询hive表中数据 -- 在ssm模块下创建新包com.ssm.callloghive.HiveService
--------------------------------------------------
package com.ssm.callloghive.service;
import com.it18zhang.ssm.domain.Calllog;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
//记得添加注解Service
@Service
public class HiveService {
//hiveserver2连接串
private static String url = "jdbc:hive2://s100:10000/" ;
//驱动程序类
private static String driverClass = "org.apache.hive.jdbc.HiveDriver" ;
static{
try {
Class.forName(driverClass);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查询最近的通话记录,使用hive进行mr查询.
*/
public Calllog findLatestCallLog(String phoneNum){
try {
Connection conn = DriverManager.getConnection(url);
Statement st = conn.createStatement();
String sql = "select * from ext_calllogs_in_hbase where id like '%"+ phoneNum+"%' order by callTime desc limit 1" ;
ResultSet rs = st.executeQuery(sql);
Calllog log = null ;
while(rs.next()){
log = new Calllog();
log.setCaller(rs.getString("caller"));
log.setCallee(rs.getString("callee"));
log.setCallTime(rs.getString("callTime"));
log.setCallDuration(rs.getString("callDuration"));
}
rs.close();
return log;
} catch (Exception e) {
e.printStackTrace();
}
return null ;
}
}
c.启动hiveserver2服务器
$> cd /soft/hive/bin/
$> ./hiveserver2 &
d.验证hiveserver2端口
$> netstat -anop | grep '10000'
f.test单元测试
----------------------
@Test
public void testHive()
{
HiveService hive = new HiveService();
hive.findLatestCallLog("17731086666");
}
5.编写前端jsp页面[findLatestCalllog.jsp],实现最近记录查询
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>查询最近的通话记录</title>
<link rel="stylesheet" type="text/css" href="../css/my.css">
</head>
<body>
<form action="<c:out value="/calllog/findLatestCalllog"/>" method="post">
<table>
<tr>
<td>电话号码 :</td>
<td><input type="text" name="caller"></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="查询"/>
</td>
</tr>
</table>
</form>
</body>
</html>
6.编写控制器
-----------------------------------------
@RequestMapping(value = "calllog/toFindLatestCalllog")
public String toFindLatestCalllogPage()
{
return "calllog/findLatestCalllog";
}
/**
* 接受参数
* @param m
* @return
*/
@RequestMapping(value = "calllog/findLatestCalllog", method = RequestMethod.POST)
public String findLatestCalllog(Model m, @RequestParam("caller") String caller)
{
Calllog log = hcs.findLatestCallLog(caller);
List<Calllog> logs = new ArrayList<Calllog>();
logs.add(log);
m.addAttribute("calllogs", logs);
return "calllog/calllogList" ;
}
7.因为添加了新的hive依赖,所以,ssm项目构建时需要添加新的包到Artifacts
File --> project structures --> ssm war exploded --> ...put to libs
8.因为新建了包,所以要在beans.xml中添加新的扫描包路径
<context:component-scan base-package="com.it18zhang.ssm.dao,com.it18zhang.ssm.service,com.ssm.callloghive.service" />
9.运行ssm-app,进入网址 http://localhost:8080/calllog/toFindLatestCalllog
10.注意事项:
SSM集成hive-jdbc访问hive的hiveserver2时,需要如下处理:
a.使用hive-jdbc-1.2.1的依赖版本
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-jdbc</artifactId>
<version>1.2.1</version>
</dependency>
b.需要集成apache-tomcat-9.0.0.M19版本,否则报编译器错误。
二、给通话记录添加名字信息
----------------------------------------------------
1.修改Calllog类,添加字段callerName和calleeName
-------------------------------------------------
package com.it18zhang.ssm.domain;
import com.it18zhang.ssm.util.CalllogUtil;
import java.text.SimpleDateFormat;
/**
* calllog的domain类 -- 标准javabean
*/
public class Calllog {
private String caller;
//主叫名字
private String callerName;
private String callee;
//被叫名字
private String calleeName;
private String callTime;
private String callDuration;
//是否是主叫
private boolean flag;
public String getCallerName() {
return callerName;
}
public void setCallerName(String callerName) {
this.callerName = callerName;
}
public String getCalleeName() {
return calleeName;
}
public void setCalleeName(String calleeName) {
this.calleeName = calleeName;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getCaller() {
return caller;
}
public void setCaller(String caller) {
this.caller = caller;
}
public String getCallee() {
return callee;
}
public void setCallee(String callee) {
this.callee = callee;
}
public String getCallTime() {
if (callTime != null) {
return CalllogUtil.formatDate(callTime);
}
return null;
}
public void setCallTime(String callTime) {
this.callTime = callTime;
}
public String getCallDuration() {
return callDuration;
}
public void setCallDuration(String callDuration) {
this.callDuration = callDuration;
}
}
2.在mysql中添加人员信息表persons,然后通过hbase的电话号码关联查询人员名字
$mysql> use mybatis;
$mysql> create table persons(id int primary key auto_increment, name varchar(100), phone varchar(20));
3.在domain中,添加实体类Person
--------------------------------------------
package com.it18zhang.ssm.domain;
public class Person {
private Integer id;
private String name;
private String phone;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
4.在dao.impl中添加
----------------------------------------
package com.it18zhang.ssm.dao.impl;
import com.it18zhang.ssm.dao.BaseDao;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.domain.User;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
*/
@Repository("personDao")
public class PersonDaoImpl extends SqlSessionDaoSupport implements BaseDao<Person> {
public void insert(Person person) {
getSqlSession().insert("persons.insert", person);
}
public void update(Person person) {
}
public void delete(Integer id) {
}
public Person selectOne(Integer id) {
return null;
}
public List<Person> selectAll() {
return getSqlSession().selectList("persons.selectAll");
}
public List<Person> selectPage(int offset, int len) {
return null;
}
public int selectCount() {
return 0;
}
//添加通过电话号码查询名字
public String selectNameByPhone(String phone) {
return getSqlSession().selectOne("persons.selectNameByPhone", phone);
}
}
5.在resources中添加映射文件PersonMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="persons">
<select id="selectAll" resultType="_Person">
select * from persons
</select>
<insert id="insert">
insert into persons(name,phone) values(#{name},#{phone})
</insert>
<!-- selectOne -->
<select id="selectNameByPhone" parameterType="string" resultMap="string">
select
name
from persons
where phone = #{phone}
</select>
</mapper>
6.编辑mybatis-config.xml配置文件,给Person类添加别名并且添加配置映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias type="com.it18zhang.ssm.domain.User" alias="_User"/>
<typeAlias type="com.it18zhang.ssm.domain.Order" alias="_Order"/>
<typeAlias type="com.it18zhang.ssm.domain.Item" alias="_Item"/>
<typeAlias type="com.it18zhang.ssm.domain.Person" alias="_Person"/>
</typeAliases>
<!-- 引入映射文件 -->
<mappers>
<mapper resource="UserMapper.xml"/>
<mapper resource="OrderMapper.xml"/>
<mapper resource="ItemMapper.xml"/>
<mapper resource="PersonMapper.xml"/>
</mappers>
</configuration>
7.添加PersonService
-------------------------------
package com.it18zhang.ssm.service;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.domain.User;
import java.util.List;
/**
*
*/
public interface PersonService extends BaseService<Person> {
public String selectNameByPhone(String phone);
}
8.添加PersonServiceImpl
---------------------------------------
package com.it18zhang.ssm.service.impl;
import com.it18zhang.ssm.dao.BaseDao;
import com.it18zhang.ssm.domain.Item;
import com.it18zhang.ssm.domain.Order;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.domain.User;
import com.it18zhang.ssm.service.PersonService;
import com.it18zhang.ssm.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
*
*/
@Service("personService")
public class PersonServiceImpl extends BaseServiceImpl<Person> implements PersonService {
@Resource(name = "personDao")
public void setDao(BaseDao<Person> dao) {
super.setDao(dao);
}
public String selectNameByPhone(String phone) {
return ((PersonDaoImpl) getDao()).selectNameByPhone(phone);
}
}
9.添加测试类,查看是否能够插入和查询人员名单
----------------------------------------------
package com.it18zhang.ssm.test;
import com.it18zhang.ssm.domain.Person;
import com.it18zhang.ssm.service.PersonService;
import com.it18zhang.ssm.service.impl.PersonServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.*;
public class TestPerson {
//电话簿
public static Map<String, String> callers = new HashMap<String, String>();
static{
callers.put("15811111111", "史让");
callers.put("18022222222", "赵嗄");
callers.put("15133333333", "张锕 ");
callers.put("13269364444", "王以");
callers.put("15032295555", "张噢");
callers.put("17731086666", "张类");
callers.put("15338597777", "李平");
callers.put("15733218888", "杜跑");
callers.put("15614209999", "任阳");
callers.put("15778421111", "梁鹏");
callers.put("18641241111", "郭彤");
callers.put("15732641111", "刘飞");
callers.put("13341101111", "段星");
callers.put("13560191111", "唐华");
callers.put("18301581111", "杨谋");
callers.put("13520401111", "温英");
callers.put("18332561111", "朱宽");
callers.put("18620191111", "刘宗");
}
@Test
public void testInsertPerson()
{
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
PersonService ps = (PersonService)ac.getBean("personService");
Set<String> keySet = callers.keySet();
for(String key : keySet)
{
String num = key;
String name = callers.get(key);
Person p = new Person();
p.setName(name);
p.setPhone(num);
ps.insert(p);
}
}
@Test
public void testFindAllPerson()
{
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
PersonService ps = (PersonService)ac.getBean("personService");
List<Person> list = ps.selectAll();
for(Person p : list)
{
System.out.println(p.getName() + ":" + p.getPhone());
}
}
@Test
public void testFindPersonNameByPhone()
{
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
PersonService ps = (PersonService)ac.getBean("personService");
String name = ps.selectNameByPhone("15338597777");
System.out.println(name);
}
}
10.修改CalllogController的findAll控制器对应的findAll方法
----------------------------------------------------------
/**
* 查询所有的calllog
* 全表扫描
* @return
*/
public List<Calllog> findAll() {
List<Calllog> list = new ArrayList<Calllog>();
try {
//扫描
Scan scan = new Scan();
ResultScanner rs = table.getScanner(scan);
Iterator<Result> it = rs.iterator();
byte[] famliy = Bytes.toBytes("f1");
byte[] callerf = Bytes.toBytes("caller");
byte[] calleef = Bytes.toBytes("callee");
byte[] callTimef = Bytes.toBytes("callTime");
byte[] callDurationf = Bytes.toBytes("callDuration");
Calllog calllog = null;
while (it.hasNext()) {
Result next = it.next();
String caller = Bytes.toString(next.getValue(famliy, callerf));
String callee = Bytes.toString(next.getValue(famliy, calleef));
String callTime = Bytes.toString(next.getValue(famliy, callTimef));
String callDuration = Bytes.toString(next.getValue(famliy, callDurationf));
calllog = new Calllog();
//rowkey
String rowkey = Bytes.toString(next.getRow());
String flag = rowkey.split(",")[3];
calllog.setFlag(flag.equals("0")?true:false);
calllog.setCaller(caller);
calllog.setCallee(callee);
calllog.setCallTime(callTime);
calllog.setCallDuration(callDuration);
//查询mysql设置主叫名字和被叫名字
String callerName = ps.selectNameByPhone(caller);
String calleeName = ps.selectNameByPhone(callee);
calllog.setCallerName(callerName);
calllog.setCalleeName(calleeName);
list.add(calllog);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
11.修改查询全部的前端jsp界面[calllogList.jsp]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>通话记录</title>
<link rel="stylesheet" type="text/css" href="../css/my.css">
</head>
<body>
<table id="t1" border="1px" class="t-1" style="width: 800px">
<tr>
<td>本机号码</td>
<td>本机名字</td>
<td>对方号码</td>
<td>对方名字</td>
<td>是否主叫</td>
<td>通话时间</td>
<td>通话时长</td>
</tr>
<c:forEach items="${calllogs}" var="u">
<tr>
<td><c:out value="${u.caller}"/></td>
<td><c:out value="${u.callerName}"/></td>
<td><c:out value="${u.callee}"/></td>
<td><c:out value="${u.calleeName}"/></td>
<td>
<c:if test="${u.caller != param.caller}">
被叫
</c:if>
<c:if test="${u.caller == param.caller}">
主叫
</c:if>
</td>
<td><c:out value="${u.callTime}"/></td>
<td><c:out value="${u.callDuration}"/></td>
</tr>
</c:forEach>
<tr>
<td colspan="5" style="text-align: right">
</td>
</tr>
</table>
</body>
</html>
三、MR运行参数配置,关闭物理内存和虚拟内存对容器的限制 -- 解决MR作业启动不起来的问题
--------------------------------------------------------------------------------
默认限制是开启的,最多分配给容器8G的物理内存,虚拟内存是物理内存的2.1倍。
[yarn-site.xml]
<property>
<name>yarn.nodemanager.resource.memory-mb</name>
<value>8192</value>
</property>
<property>
<name>yarn.nodemanager.pmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-check-enabled</name>
<value>false</value>
</property>
<property>
<name>yarn.nodemanager.vmem-pmem-ratio</name>
<value>2.1</value>
</property>
四、实现web界面实时刷新通话记录的功能
-------------------------------------------------
1.实现动态产生通话日志,前端实时刷新获取
2.重写日志生成模块
a.将代码选项外部化,通过配置文件[gendata.conf]动态加载
log.file=/home/ubuntu/calllog/calllog.log
call.duration.max=600
call.duration.format=000
call.year=2018
call.time.format=yyyy/MM/dd HH:mm:ss
gen.data.interval.ms=5000
b.新建工具类PropertiesUtil
--------------------------------
package calllog.gen.main;
import java.io.InputStream;
import java.util.Properties;
/**
*
*/
public class PropertiesUtil {
static Properties prop ;
static{
try {
InputStream in = ClassLoader.getSystemResourceAsStream("gendata.conf");
prop = new Properties();
prop.load(in);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getProp(String key){
return prop.getProperty(key) ;
}
public static String getString(String key){
return prop.getProperty(key) ;
}
public static int getInt(String key){
return Integer.parseInt(prop.getProperty(key)) ;
}
}
c.重写app类
---------------------------------------
package calllog.gen.main;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.*;
public class App {
public static Random random = new Random();
//电话簿
public static Map<String, String> callers = new HashMap<String, String>();
//电话号码
public static List<String> phoneNumbers = new ArrayList<String>();
static{
callers.put("15811111111", "史让");
callers.put("18022222222", "赵嗄");
callers.put("15133333333", "张锕 ");
callers.put("13269364444", "王以");
callers.put("15032295555", "张噢");
callers.put("17731086666", "张类");
callers.put("15338597777", "李平");
callers.put("15733218888", "杜跑");
callers.put("15614209999", "任阳");
callers.put("15778421111", "梁鹏");
callers.put("18641241111", "郭彤");
callers.put("15732641111", "刘飞");
callers.put("13341101111", "段星");
callers.put("13560191111", "唐华");
callers.put("18301581111", "杨谋");
callers.put("13520401111", "温英");
callers.put("18332561111", "朱宽");
callers.put("18620191111", "刘宗");
phoneNumbers.addAll(callers.keySet());
}
public static void main(String [] args)
{
genCallLog();
}
/**
* 生成通话日志
*/
private static void genCallLog() {
try {
//文件写入器
FileWriter fw = new FileWriter(PropertiesUtil.getString("log.file"), true);
while (true) {
//主叫
String caller = phoneNumbers.get(random.nextInt(callers.size()));
String callerName = callers.get(caller);
//被叫 (!= 主叫)
String callee = phoneNumbers.get(random.nextInt(callers.size()));
while (callee.equals(caller)) {
callee = phoneNumbers.get(random.nextInt(callers.size()));
}
String calleeName = callers.get(callee);
//通话时长(<10min)
int duration = random.nextInt(PropertiesUtil.getInt("call.duration.max")) + 1;
DecimalFormat df = new DecimalFormat();
df.applyPattern(PropertiesUtil.getString("call.duration.format"));
String dur = df.format(duration);
//通话时间timeStr
int year = PropertiesUtil.getInt("call.year");;
int month = random.nextInt(12);
int day = random.nextInt(29) + 1;
int hour = random.nextInt(24);
int min = random.nextInt(60);
int sec = random.nextInt(60);
Calendar calendar = Calendar.getInstance();
calendar.set(year,month,day,hour,min,sec);
Date date = calendar.getTime();
//如果时间超过今天就重新qushijian取时间.
Date now = new Date();
if (date.compareTo(now) > 0) {
continue ;
}
SimpleDateFormat dfs = new SimpleDateFormat();
dfs.applyPattern(PropertiesUtil.getString("call.time.format"));
String timeStr = dfs.format(date);
//通话日志
//String callLog = caller + "," + callerName + "," + callee + "," + calleeName + "," + timeStr + "," + dur;
String callLog = caller + "," + callee + "," + timeStr + "," + dur;
fw.write(callLog+ "\r\n");
fw.flush();
System.out.println("callLog: " + callLog);
Thread.sleep(PropertiesUtil.getInt("gen.data.interval.ms"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
d.使用maven打包,并拷贝到/home/ubuntu/calllog目录下
e.运行 ./calllog.sh,查看calllog.log的生成情况
3.ajax:java脚本的异步访问 + xml
a.ssm模块中引入fastjson
b.添加maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
c.模拟最底层的request和response,实现直接返回json串到前端页面
1)编写CalllogController.findAllJson
/**
* 模拟最底层的request和response,实现直接返回json串到前端页面
*/
@RequestMapping("calllog/json/findAll")
public String findAllJson(HttpServletResponse response)
{
try {
List<Calllog> list = cs.findAll();
String jsonStr = JSONArray.toJSONString(list);
//设定回应的数据类型是json串
response.setContentType("application/json");
//得到发送给客户端的输出流
ServletOutputStream sos = response.getOutputStream();
sos.write(jsonStr.getBytes());
sos.flush();
sos.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
2)运行ssm app,打开网址http://localhost:8080/calllog/json/findAll测试
d.web集成jQuery库,实现ajax访问后台数据,实现动态刷新
1)引入web/js/jquery-3.2.0.min.js
2)修改calllogList.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<title>通话记录</title>
<link rel="stylesheet" type="text/css" href="../css/my.css">
<script type="text/javascript" src="../js/jquery-3.2.0.min.js"></script>
<script type="text/javascript" >
//定义函数
function refreshTable(){
$("#t1 tbody").empty();
$.getJSON("/calllog/json/findAll", function (data) {
$.each(data, function (i, obj) {
var str = "<tr><td>" + obj.caller + "</td>";
str = str + "<td> " + obj.callerName + "</td>";
str = str + "<td> " + obj.callee + "</td>";
str = str + "<td> " + obj.calleeName + "</td>";
str = str + "<td></td>";
str = str + "<td> " + obj.callTime + "</td>";
str = str + "<td> " + obj.callDuration + "</td>";
str = str + "</tr>";
$("#t1 tbody").append(str);
});
});
}
$(function(){
setInterval(refreshTable, 2000);
})
</script>
</head>
<body>
<input id="btnCleanTable" type="button" value="清除表格"><br>
<table id="t1" border="1px" class="t-1" style="width: 800px">
<thead>
<tr>
<td>本机号码</td>
<td>本机名字</td>
<td>对方号码</td>
<td>对方名字</td>
<td>是否主叫</td>
<td>通话时间</td>
<td>通话时长</td>
</tr>
</thead>
<tbody>
<c:forEach items="${calllogs}" var="u">
<tr>
<td><c:out value="${u.caller}"/></td>
<td><c:out value="${u.callerName}"/></td>
<td><c:out value="${u.callee}"/></td>
<td><c:out value="${u.calleeName}"/></td>
<td>
<c:if test="${u.caller != param.caller}">
被叫
</c:if>
<c:if test="${u.caller == param.caller}">
主叫
</c:if>
</td>
<td><c:out value="${u.callTime}"/></td>
<td><c:out value="${u.callDuration}"/></td>
</tr>
</c:forEach>
<tr>
<td colspan="7" style="text-align: right">
</td>
</tr>
</tbody>
</table>
</body>
</html>