嵌入式部署流程
在项目的web.xml中配置报表控件的servlet及servlet-mapping。
之所以要用到程序数据源,是因为组内的项目使用的数据库是mongodb,而帆软的mongodb插件支持的查询深度还不够,所以采用程序数据源的方式来处理。
帆软报表控件程序数据源的写法说白了比较简单,就是实现AbstractTableData这个抽象类,然后实现其中的4个抽象方法即可。
但是使用起来不是很顺畅,原因在于:
- 首先要在在IDE中写程序数据源的代码;
- 然后编译对应的代码生成class文件;
- 将class文件拷贝到帆软报表设计器对应的classes目录下;
- 打开报表设计器选择程序数据源(class文件)进行报表设计;
- 设计完成的报表拷贝到项目的reportlets目录下;
可以看到,需要不停地在IDE和报表设计器之间切换,还需要在资源管理器中移动文件。可能这是目前嵌入式部署不得不面对的问题,如果是独立部署的话,可能就仅仅需要将classes文件拷贝到帆软报表设计器对应目录下即可。
在项目中写了一个数据源,如下:
public class TestDemo extends AbstractTableData {
private String[] columnNames;
private Object[][] rowData;
public TestDemo() {
String[] columnNames = {"Name", "Score"};
Object[][] datas = {{"Alex", new Integer(15)}, {"Helly", new Integer(22)}, {"Bobby", new Integer(99)}};
this.columnNames = columnNames;
this.rowData = datas;
}
@Override
public int getColumnCount() throws TableDataException {
return columnNames.length;
}
@Override
public String getColumnName(int columnIndex) throws TableDataException {
return columnNames[columnIndex];
}
@Override
public int getRowCount() throws TableDataException {
return rowData.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rowData[rowIndex][columnIndex];
}
}
不涉及业务,不涉及mongodb数据库,仅仅测试一下可行性。
按照上面的步骤设计完报表之后,将报表拷贝回项目reportlets目录,运行:
可以看到,程序运行OK,报表也能正常显示。
由于写了程序数据源之后,是要放到报表设计器目录下进行报表设计才能看到结果的,这样给调试带来了一些难度。
解决办法就是:
- main方法进行跟踪调试;
- 单元测试;
// 数据源一共有多少列(多少个字段)
public abstract int getColumnCount() throws TableDataException;
// 对应index的那一列的列名(字段名)是什么
public abstract String getColumnName(int index) throws TableDataException;
// 数据源一共有多少行(多少条记录)
public abstract int getRowCount() throws TableDataException;
// 对应rowIndex的那一行的第columnIndex列的数值
public abstract Object getValueAt(int rowIndex, int columnIndex);
从抽象类接口可以看出,四个抽象方法意义很明确,使用main方法或者单元测试来进行验证的话,跟踪调试查看的数据没有问题,那么报表中的数据应该就没有问题。
按照从前的思维,希望能够使用数据库连接池,复用数据库连接。
然后发现MongoDB的MongoClient和关系型数据库的连接还是有一些区别的,可能开销没有那么大。因此在数据源中直接new新的MongoClient来使用。
再后来仔细想想,我们的程序数据源不是运行在我们的Web容器中,不是使用Spring进行管理,而是编译成为class文件之后拷贝到帆软的设计器目录下使用的,那个时候,怎么使用数据库连接池中的连接资源呢?
所以,目前还是在每个数据源中直接new连接的方式来操作,有没有更好的办法抽空问问帆软的技术支持。
按照业务,从我们自己的mongo数据库中获取数据,写了如下一个数据源:
public class BQItemDataSource extends AbstractTableData {
private enum Field_Name {
CODE("code"), DESCRIPTION("description"), SPEC("spec"), UNIT("unit"), JLGZ("jlgz"),
TYPE("type"), QUANTITY("quantity");
private String fieldName;
private Field_Name(String fieldName) {
this.fieldName = fieldName;
}
@Override
public String toString() {
return fieldName;
}
}
private String[] columnNames;
private ArrayList> rowData;
public BQItemDataSource() {
// setDefaultParameters(new Parameter[] {new Parameter("ObjectId")});
columnNames = new String[Field_Name.values().length];
int i = 0;
for (Field_Name fieldName : Field_Name.values()) {
columnNames[i++] = fieldName.toString();
}
}
@Override
public int getColumnCount() throws TableDataException {
return columnNames.length;
}
@Override
public String getColumnName(int columnIndex) throws TableDataException {
return columnNames[columnIndex];
}
@Override
public int getRowCount() throws TableDataException {
init();
return rowData.size();
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
init();
if (columnIndex >= columnNames.length) {
return null;
}
if (rowIndex >= rowData.size()) {
return null;
}
return rowData.get(rowIndex).get(columnNames[columnIndex]);
}
public void init() {
if (rowData != null) {
return;
}
rowData = new ArrayList>();
// String objectId = parameters[0].getValue().toString();
String objectId = "5959b01b9107541bbc5cb98b";
JSONArray dataArray = getMongoTableData();
for (int i = 0; i < dataArray.size(); i++) {
JSONObject dataObj = dataArray.getJSONObject(i);
LinkedHashMap row = new LinkedHashMap();
if (dataObj.containsKey(Field_Name.CODE.toString())) {
row.put(Field_Name.CODE.toString(), dataObj.getString(Field_Name.CODE.toString()));
} else {
row.put(Field_Name.CODE.toString(), null);
}
if (dataObj.containsKey(Field_Name.DESCRIPTION.toString())) {
row.put(Field_Name.DESCRIPTION.toString(), dataObj.getString(Field_Name.DESCRIPTION.toString()));
} else {
row.put(Field_Name.DESCRIPTION.toString(), null);
}
if (dataObj.containsKey(Field_Name.SPEC.toString())) {
row.put(Field_Name.SPEC.toString(), dataObj.getString(Field_Name.SPEC.toString()));
} else {
row.put(Field_Name.SPEC.toString(), null);
}
if (dataObj.containsKey(Field_Name.UNIT.toString())) {
row.put(Field_Name.UNIT.toString(), dataObj.getString(Field_Name.UNIT.toString()));
} else {
row.put(Field_Name.UNIT.toString(), null);
}
if (dataObj.containsKey(Field_Name.JLGZ.toString())) {
row.put(Field_Name.JLGZ.toString(), dataObj.getString(Field_Name.JLGZ.toString()));
} else {
row.put(Field_Name.JLGZ.toString(), null);
}
if (dataObj.containsKey(Field_Name.TYPE.toString())) {
row.put(Field_Name.TYPE.toString(), dataObj.getString(Field_Name.TYPE.toString()));
} else {
row.put(Field_Name.TYPE.toString(), null);
}
if (dataObj.containsKey(Field_Name.QUANTITY.toString())) {
row.put(Field_Name.QUANTITY.toString(), dataObj.getDouble(Field_Name.QUANTITY.toString()));
} else {
row.put(Field_Name.QUANTITY.toString(), null);
}
rowData.add(row);
}
}
private JSONArray getMongoTableData() {
MongoClient mongoClient=new MongoClient( "localhost" , 27017 );
MongoDatabase db = mongoClient.getDatabase("gbq");
MongoCollection collection = db.getCollection("project");
Document doc= collection.find(eq("_id", new ObjectId("5959b01b9107541bbc5cb98b"))).first();
if(doc!=null){
JSONObject data = JSONObject.fromObject(doc.get("data"));
return data.getJSONArray("bqItem");
}else{
return new JSONArray();
}
}
public void release() throws Exception {
super.release();
this.rowData = null;
}
// public static void main(String args[]) {
// BQItemDataSource dataSource = new BQItemDataSource();
// dataSource.init();
// try {
// System.out.println(dataSource.getColumnCount());
// for (int i = 0; i < dataSource.getColumnCount(); i++) {
// System.out.println(dataSource.getColumnName(i));
// }
// for (int i = 0; i < dataSource.getRowCount(); i++) {
// for (int j = 0; j < dataSource.getColumnCount(); j++) {
// System.out.print(dataSource.getValueAt(i, j) + "@");
// }
// System.out.println();
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
}
如上的数据源中,没有使用传参方式,而是直接在代码中把参数写死了。实际上可以采用传参的方式,在帆软报表设计器中可以设定参数。
但是,上面的数据源class拷贝到帆软设计器目录下的时候,字段是可以获取到的,但是数据是获取不到的。
预览报表的时候页面是空的。
想了很久,没有得到答案。周末的时候突然灵光一闪,想到一个可能的原因:
- 我们的程序数据源中使用了JSON和MongoClient,这两个东西是依赖于某些jar包的;
- 程序数据源的class文件拷贝到帆软目录下,在帆软的内置服务器中运行,而这个内置服务器未必引用了对应的jar包;
- 那为什么这个class数据源还是可以引用,并且能看到字段呢?因为帆软中引用的是class文件,而不是源代码,用到字段的时候调用getColumnCount和getColumnName这两个方法,是不受影响的;而显示数据的时候,调用getRowCount和getValueAt两个方法,内部使用了JSON和MongoClient相关的接口,可能内部已经报异常了。
解决办法:
- 查看程序数据源的jar包依赖(使用IDEA的Maven插件查看依赖关系图):
- 将所依赖的jar包拷贝到帆软的lib目录下:
从上图可以看出,直接依赖的包有json-lib和mongodb-driver;
因mongdodb而间接依赖的包有mongodb-driver-core和bson;
因json而间接依赖的包有commons-beanutils、commons-logging、commons-lang、commons-collections、ezmorph。
完成以上步骤之后,在帆软报表设计器中设计完报表点击预览,可以看到报表的内容:
至此,程序数据源的路已经走通,可以尽情发挥业务编程能力,编写数据源了!