2021SC@SDUSC
软件工程应用与实践——OpenMeetings项目分析(七):
上周,分析了web包下的web项目webapp文件夹下的代码,本周开始分析web包下的java代码;
web包的java包里包含了admin,app,common,data,pagaes,room,user,util八个包,每个包的代码量都是之前webservice下的十倍左右,工作量巨大,由于篇幅与经历原因,只对关键代码做分析,不去完全深究每个类的作用。
common包通常是所有其他包都需要用到的,因此本周,从common入手:
common包结构:
Menu包:
顾名思义,menu包是与菜单栏有关的类;
可以看出,OmMenuItem类是RoomMenuItem类和MainMenuItem类的父类;
OmMenuItem:
public class OmMenuItem extends MenuItem {
private static final long serialVersionUID = 1L;
private String desc;
private boolean top;
public OmMenuItem(String title, List<IMenuItem> items) {
super(title, items);
setTop(true);
}
public OmMenuItem(String title, String desc) {
super(title);
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public boolean isTop() {
return top;
}
public OmMenuItem setTop(boolean top) {
this.top = top;
return this;
}
}
private static final long serialVersionUID = 1L;这一句在之前的代码里都有,我说知道的仅仅是为了便于将此类进行序列化,今天又遇到了,因此去查找资料搜索了一番,附在此类最后做补充。
然后,这个类含有两个不同参数的构造方法两个成员变量以及getset方法,不再赘述。
补充:
序列化相关概念以及原因:
1、序列化的目的:为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来,这是java中的提供的保存对象状态的机制—序列化。
场景:
1、当想把的内存中的对象状态保存到一个文件中或者数据库中表(内存到数据库)
2、当想用套接字在网络上传送对象(网络传输)(或者不同线程之间通讯)
3、当想通过RMI传输对象的时候(分布式传输)
2、java实现Serializable接口
public interface Serializable {}
并没有需要去实现的方法,相当于一个标识,可以被序列化。一个java中的类只有实现了Serializable接口,它的对象才是可序列化的。
3、serialVersionUID
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
显式地定义serialVersionUID有两种用途:
a. 在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;
b. 在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
4、 private static final long serialVersionUID = 1L; 的意义
首先,不给定的话因为不同的JVM之间的序列化算法是不一样的,不同的JDK也可能不一样,不利于程序的移植,可能会让反序列化失败。
实现java.io.Serializable这个接口是为序列化,serialVersionUID 用来表明实现序列化类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会出错。
RoomMenuItem:
public class RoomMenuItem extends OmMenuItem {
private static final long serialVersionUID = 1L;
public RoomMenuItem(String name, String desc) {
super(name, desc);
}
public RoomMenuItem(String name, String desc, String icon) {
super(name, desc);
setIcon(icon);
}
public RoomMenuItem(String name, String desc, boolean enabled) {
super(name, desc);
setEnabled(enabled);
}
}
此类继承自OmMenuItem类,包含三个不同参数的构造方法,以便根据传入参数不同生成不同的对象;
而其中的setIcom以及setEnabled方法均是来自com.googlecode.wicket.jquery.ui.widget.menu.AbstractMenuItem 下的方法
MainMenuItem:
public class MainMenuItem extends OmMenuItem {
private static final long serialVersionUID = 1L;
private MenuActions action;
private MenuParams params;
public MainMenuItem(String lbl, String title, MenuActions action, MenuParams param) {
super(Application.getString(lbl), Application.getString(title));
this.action = action;
this.params = param != null ? param : MenuParams.publicTabButton;
}
public void onClick(MainPanel main, AjaxRequestTarget target) {
main.updateContents(new OmUrlFragment(action, params), target);
}
}
包含一个构造方法,构造方法里通过super调用了父类的构造方法,Application是app包下的一个实体类
onclick方法,接受一个mainpanel和ajaxrequesttarget参数,调用mainpanel的内置方法进行内容的更新;
MenuPanel:
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License") + you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openmeetings.web.common.menu;
import static org.apache.openmeetings.util.OpenmeetingsVariables.ATTR_CLASS;
import static org.apache.openmeetings.util.OpenmeetingsVariables.ATTR_TITLE;
import java.util.List;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.util.string.Strings;
import com.googlecode.wicket.jquery.core.Options;
import com.googlecode.wicket.jquery.ui.widget.menu.IMenuItem;
import com.googlecode.wicket.jquery.ui.widget.menu.Menu;
/**
* Loads the menu items into the main area
*
* @author sebawagner
*
*/
public class MenuPanel extends Panel {
private static final long serialVersionUID = 1L;
public MenuPanel(String id, List<IMenuItem> menus) {
super(id);
setOutputMarkupPlaceholderTag(true);
setMarkupId(id);
add(new Menu("menu", menus, new Options().set("icons", "{ submenu: 'ui-icon-triangle-1-s' }")
.set("position", "{ my: 'left top', at: 'left bottom'}"))
{
private static final long serialVersionUID = 1L;
@Override
protected void addMenuItem(ListItem<IMenuItem> item, IMenuItem menuItem) {
super.addMenuItem(item, menuItem);
OmMenuItem m = (OmMenuItem)menuItem;
item.add(AttributeModifier.append(ATTR_CLASS, m.isTop() ? "top" : "sub"));
if (!Strings.isEmpty(m.getDesc())) {
item.add(AttributeModifier.append(ATTR_TITLE, m.getDesc()));
}
if (!Strings.isEmpty(m.getIcon())) {
item.add(AttributeModifier.append(ATTR_CLASS, m.getIcon()));
}
}
});
}
public void update(IPartialPageRequestHandler target) {
target.add(this);
}
}
此类继承自Panel类,显然是一个用户界面有关的类,
在构造方法里通过接受的List类型的参数,调用org.apache.wicket.MarkupContainer 的内置方法add进行菜单项目的添加
MenuPanel.html:
<html xmlns:wicket="http://wicket.apache.org">
<wicket:panel>
<div wicket:id="menu" class="ui-widget-header"></div>
</wicket:panel>
</html>
此html文件只包含了一个,jquery-ui里的定义好的面板组件;
Tree包:
结构:
FloderPanel:
public class FolderPanel extends Panel implements IDraggableListener, IDroppableListener {
private static final long serialVersionUID = 1L;
private static final String CSS_CLASS_FILE = "file ";
private static final String PARAM_MOD = "mod";
private static final String PARAM_SHIFT = "s";
private static final String PARAM_CTRL = "c";
private final StyleBehavior styleClass;
private final FileTreePanel treePanel;
public FolderPanel(String id, final IModel<BaseFileItem> model, final FileTreePanel treePanel) {
super(id, model);
this.treePanel = treePanel;
styleClass = new StyleBehavior();
}
@Override
protected void onInitialize() {
super.onInitialize();
final BaseFileItem f = (BaseFileItem)getDefaultModelObject();
boolean editable = !treePanel.isReadOnly() && !f.isReadOnly();
final String selector = JQueryWidget.getSelector(this);
if (f.getType() == Type.Folder && editable) {
add(new DroppableBehavior(
selector
, new Options()
.set("hoverClass", Options.asString("ui-state-hover"))
.set("accept", Options.asString(getDefaultModelObject() instanceof Recording ? ".recorditem" : ".fileitem"))
, this));
}
if (f.getId() != null && !treePanel.isReadOnly()) {
add(new DraggableBehavior(
selector
, new Options()
.set("revert", "treeRevert")
.set("cursor", Options.asString("move"))
.set("helper", "dragHelper")
.set("cursorAt", "{left: 40, top: 18}")
.set("containment", Options.asString(treePanel.getContainment()))
, this));
}
Component name = f.getId() == null || !editable ? new Label("name", f.getName()) : new AjaxEditableLabel<String>("name", Model.of(f.getName())) {
private static final long serialVersionUID = 1L;
@Override
protected String getLabelAjaxEvent() {
return "dblclick";
}
@Override
protected void onSubmit(AjaxRequestTarget target) {
super.onSubmit(target);
f.setName(getEditor().getModelObject());
if (f instanceof Recording) {
getBean(RecordingDao.class).update((Recording)f);
} else {
getBean(FileItemDao.class).update((FileItem)f);
}
}
};
add(name);
add(AttributeModifier.append(ATTR_TITLE, f.getName()));
add(styleClass);
add(new AjaxEventBehavior("click") {
private static final long serialVersionUID = 1L;
@Override
public void onEvent(AjaxRequestTarget target) {
onClick(target, f);
}
@Override
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getDynamicExtraParameters().add(
String.format("return {%s: JSON.stringify({%s: attrs.event.shiftKey, %s: attrs.event.ctrlKey})};"
, PARAM_MOD, PARAM_SHIFT, PARAM_CTRL));
}
});
}
private static void moveAll(final FileTreePanel treePanel, AjaxRequestTarget target, BaseFileItem p) {
for (Entry<String, BaseFileItem> e : treePanel.getSelected().entrySet()) {
move(treePanel, target, p, e.getValue());
}
}
private static void move(final FileTreePanel treePanel, AjaxRequestTarget target, BaseFileItem p, BaseFileItem f) {
Long pid = p.getId();
if (pid != null && pid.equals(f.getId())) {
return;
}
f.setParentId(pid);
f.setOwnerId(p.getOwnerId());
f.setRoomId(p.getRoomId());
f.setGroupId(p.getGroupId());
if (f instanceof Recording) {
getBean(RecordingDao.class).update((Recording)f);
} else {
getBean(FileItemDao.class).update((FileItem)f);
}
treePanel.updateNode(target, f);
}
private void onClick(AjaxRequestTarget target, BaseFileItem f) {
String mod = getRequest().getRequestParameters().getParameterValue(PARAM_MOD).toOptionalString();
boolean shift = false, ctrl = false;
if (!Strings.isEmpty(mod)) {
JSONObject o = new JSONObject(mod);
shift = o.optBoolean(PARAM_SHIFT);
ctrl = o.optBoolean(PARAM_CTRL);
}
treePanel.select(f, target, shift, ctrl);
if (Type.Folder == f.getType() && treePanel.tree.getState(f) == State.COLLAPSED) {
treePanel.tree.expand(f);
} else {
treePanel.update(target, f);
}
}
private CharSequence getItemStyle() {
final BaseFileItem f = (BaseFileItem)getDefaultModelObject();
boolean open = State.EXPANDED == treePanel.tree.getState(f);
StringBuilder style = new StringBuilder("big om-icon ");
if (f.getId() == null) {
style.append(CSS_CLASS_FILE).append(f.getHash().indexOf("my") > -1 ? "my " : "public ");
} else {
if (!f.exists()) {
style.append("broken ");
}
switch(f.getType()) {
case Folder:
style.append(CSS_CLASS_FILE).append(open ? "folder-open " : "folder ");
break;
case Image:
style.append(CSS_CLASS_FILE).append("image ");
break;
case PollChart:
style.append(CSS_CLASS_FILE).append("chart ");
break;
case WmlFile:
style.append(CSS_CLASS_FILE).append("wml ");
break;
case Video:
case Recording:
{
style.append("recording ");
if (f instanceof Recording) {
Status st = ((Recording)f).getStatus();
if (Status.RECORDING == st || Status.CONVERTING == st) {
style.append("processing");
}
}
}
break;
case Presentation:
style.append(CSS_CLASS_FILE).append("doc ");
break;
default:
break;
}
}
if (treePanel.isSelected(f)) {
style.append("ui-state-active ");
}
String cls = f instanceof Recording ? "recorditem " : "fileitem ";
style.append(f.isReadOnly() ? "readonlyitem " : cls);
return style;
}
@Override
public boolean isStopEventEnabled() {
return false;
}
@Override
public void onDragStart(AjaxRequestTarget target, int top, int left) {
// noop
}
@Override
public void onDragStop(AjaxRequestTarget target, int top, int left) {
// noop
}
@Override
public boolean isOverEventEnabled() {
return false;
}
@Override
public boolean isExitEventEnabled() {
return false;
}
@Override
public void onOver(AjaxRequestTarget target, Component component) {
// noop
}
@Override
public void onExit(AjaxRequestTarget target, Component component) {
// noop
}
@Override
public void onDrop(AjaxRequestTarget target, Component component) {
Object o = component.getDefaultModelObject();
if (o instanceof BaseFileItem) {
BaseFileItem p = (BaseFileItem)getDefaultModelObject();
BaseFileItem f = (BaseFileItem)o;
if (treePanel.isSelected(f)) {
moveAll(treePanel, target, p);
} else {
move(treePanel, target, p, f);
}
treePanel.updateNode(target, p);
}
target.add(treePanel.trees);
}
private class StyleBehavior extends Behavior {
private static final long serialVersionUID = 1L;
@Override
public void onComponentTag(Component component, ComponentTag tag) {
tag.put(ATTR_CLASS, getItemStyle());
}
}
}
FolderPanel类继承自AWT包下的Panel类,实现了IDraggableListener, IDroppableListener这两个自定义的监听接口;
构造方法:形参:String id, final IModel model, final FileTreePanel treePanel;作用:用于对FolderPanel进行初始化,
onInitialize方法:是protected类型的方法;形参:空;返回值:空;作用:对文件夹面板的文件项目进行加载
moveAll方法:移动文件项目所需要调用的方法,形参为:final FileTreePanel treePanel, AjaxRequestTarget target, BaseFileItem p;通过传入的文件项目树。以及前端的请求目标以及基础文件项目,利用for循环调用move进行文件项目的移动;
move方法:同样用来移动文件项目的,接受形参为:final FileTreePanel treePanel, AjaxRequestTarget target, BaseFileItem p, BaseFileItem f,先检测用户的登录状态,通过后,为传入的基本文件项f设置父指针,设置所有者号码,所属房间号码,以及所属组号码;最后判断f的类型分为是Recording和不是recording两种进行文件树的结点的更新
onclick函数:接收形参为:AjaxRequestTarget target, BaseFileItem f,返回值为空;通过传入的请求状态以及基础文件项定义了一些列在前端的文件项目被点击时执行的动作;
getItemStyle函数:返回值为一个CharSequence类型的对象,形参为空,用于判断一个文件的类型,通过StringBuilder类型进行字符串的动态添加;
后面的从isStopEventEnabled到onExit这些方法均没有定义方法体,不做过多解释。
onDrop方法:形参为AjaxRequestTarget target, Component component:,返回值为空,作用是删除某个文件项,此处采用的方法是将要删除的项目的各个属性设置为空;判断component的类型,生成一个属性值全为空的文件项,并让他在文件树中替代要删除的文件项目;
最后,此类还包含了一个内部类,StyleBehavior,继承自Behavior,重写了onComponentTag方法;
FileItemPanel:
public class FileItemPanel extends FolderPanel {
private static final long serialVersionUID = 1L;
private final WebMarkupContainer errors = new WebMarkupContainer("errors");
public FileItemPanel(String id, final IModel<BaseFileItem> model, final FileTreePanel fileTreePanel) {
super(id, model, fileTreePanel);
BaseFileItem f = model.getObject();
long errorCount = getBean(FileItemLogDao.class).countErrors(f);
boolean visible = errorCount != 0;
if (BaseFileItem.Type.Recording == f.getType()) {
Recording r = (Recording)f;
visible |= (Status.RECORDING != r.getStatus() && Status.CONVERTING != r.getStatus() && !f.exists());
} else {
visible |= !f.exists();
}
errors.add(new AjaxEventBehavior(EVT_CLICK) {
private static final long serialVersionUID = 1L;
@Override
protected void onEvent(AjaxRequestTarget target) {
fileTreePanel.errorsDialog.setDefaultModel(model);
fileTreePanel.errorsDialog.open(target);
}
}).setVisible(visible);
add(errors);
}
}
继承自FloderPanel,仅包含了一个构造方法,比较简单,在调用父类构造方法的同时,调用org.apache.wicket.MarkupContainer的add方法将检测到的错误的信息上传;
FileItemTree:
public class FileItemTree extends DefaultNestedTree<BaseFileItem> {
private static final long serialVersionUID = 1L;
final FileTreePanel treePanel;
public FileItemTree(String id, FileTreePanel treePanel, ITreeProvider<BaseFileItem> tp) {
super(id, tp);
this.treePanel = treePanel;
setItemReuseStrategy(new ReuseIfModelsEqualStrategy());
}
@Override
public OmTreeProvider getProvider() {
return (OmTreeProvider)super.getProvider();
}
public void refreshRoots(boolean all) {
modelChanging();
getModelObject().clear();
modelChanged();
getProvider().refreshRoots(all);
replace(newSubtree("subtree", Model.of((BaseFileItem)null)));
}
@Override
protected Component newContentComponent(String id, IModel<BaseFileItem> lm) {
BaseFileItem r = lm.getObject();
return Type.Folder == r.getType() || r.getId() == null
? new FolderPanel(id, lm, treePanel)
: new FileItemPanel(id, lm, treePanel);
}
}
结构:
构造方法:通过super调用父类的构造方法,以便实例化此类的对象;
getProvider方法:形参为空,返回值为OmTreeProvider,调用了父类的getProvider方法并强转成OmTreeProvider对象返回;
refreshRoots方法:用于刷新系统状态的方法;通过org.apache.wicket.Component的方法进行系统状态的刷新,基本思路是用更新后的状态树去替换前一个状态树
newContentComponent方法:形参为String id, IModel lm,根据id以及lm,创建一个新的Component对象并返回,通过判断继承自父类的Type属性与传入的lm的getObject属性是否一致来决定返回的具体是FolderPanel还是FileItemPanel对象。
DownloadMenuItem:
public class DownloadMenuItem extends MenuItem {
private static final long serialVersionUID = 1L;
private final String ext;
private final FileTreePanel tree;
public DownloadMenuItem(String title, FileTreePanel tree, String ext) {
super(title, JQueryIcon.ARROWTHICKSTOP_1_S);
this.ext = ext;
this.tree = tree;
}
@Override
public boolean isEnabled() {
File f = null;
if (tree.getSelected().size() == 1) {
f = tree.getLastSelected().getFile(ext);
}
return f != null && f.exists();
}
@Override
public void onClick(AjaxRequestTarget target) {
BaseFileItem fi = tree.getLastSelected();
File f = ext == null && (Type.Image == fi.getType() || Type.Presentation == fi.getType())
? fi.getOriginal() : fi.getFile(ext);
if (f != null && f.exists()) {
tree.dwnldFile = f;
tree.downloader.initiate(target);
}
}
}
此类继承自com.googlecode.wicket.jquery.ui.widget.menu包下的MenuItem类;用于提供下载功能的菜单,包含了三个public方法,以及三个成员变量;
构造方法:用super调用父类的构造方法来初始化此类的实例对象;
isEnabled方法:重写的父类方法;判断某个文件项目是否已经启用,
onClick方法:再单击下载按钮时此类将要执行的方法为判断要下载的文件是否存在然后调用FileTreePanel的dwnldFile方法进行下载;
ConvertingErrorsDialog:
public class ConvertingErrorsDialog extends AbstractDialog<BaseFileItem> {
private static final long serialVersionUID = 1L;
private final WebMarkupContainer container = new WebMarkupContainer("container");
private final Label message = new Label("message", Model.of((String)null));
private final ListView<FileItemLog> logView = new ListView<FileItemLog>("row") {
private static final long serialVersionUID = 1L;
@Override
protected void populateItem(ListItem<FileItemLog> item) {
FileItemLog l = item.getModelObject();
item.add(new Label("exitCode", l.getExitCode()));
item.add(new Label("message", l.getMessage()));
if (!l.isOk()) {
item.add(AttributeModifier.append(ATTR_CLASS, "alert"));
}
if (l.isWarn()) {
item.add(AttributeModifier.append(ATTR_CLASS, "warn"));
}
}
};
public ConvertingErrorsDialog(String id, IModel<BaseFileItem> model) {
super(id, "", model);
add(container.add(message.setVisible(false), logView.setVisible(false)).setOutputMarkupId(true));
}
@Override
protected void onInitialize() {
getTitle().setObject(getString("887"));
super.onInitialize();
}
@Override
public int getWidth() {
return 600;
}
@Override
public boolean isResizable() {
return true;
}
@Override
public boolean isModal() {
return true;
}
@Override
protected void onOpen(IPartialPageRequestHandler handler) {
BaseFileItem f = getModelObject();
setTitle(handler, Model.of(getString(f.getType() == BaseFileItem.Type.Recording ? "887" : "convert.errors.file")));
List<FileItemLog> logs = getBean(FileItemLogDao.class).get(f);
if (f.getHash() == null) {
message.setVisible(true);
message.setDefaultModelObject(getString("888"));
} else if (!f.exists()) {
message.setVisible(true);
message.setDefaultModelObject(getString(f.getType() == BaseFileItem.Type.Recording ? "1595" : "convert.errors.file.missing"));
} else {
message.setVisible(false);
}
if (!logs.isEmpty()) {
logView.setVisible(false);
logView.setList(logs).setVisible(true);
}
handler.add(container);
super.onOpen(handler);
}
@Override
public void onClose(IPartialPageRequestHandler handler, DialogButton button) {
//no-op
}
@Override
protected List<DialogButton> getButtons() {
return new ArrayList<>();
}
}
此类继承自AbstractDialog,结构如下:
构造方法:调用父类构造方法实例化对象,同时调用org.apache.wicket.MarkupContainer的add方法进行container和logView信息的添加;
onInitialize方法:重写的父类方法,形参以及返回值都为空,用super调用父类onInitialize进行转换错误的对话框的初始化;
onOpen方法:定义了用于后续设置的基础文件项的对象后,调用了setTitle方法为转换对话框设置了题头,然后通过判断getModelObject获得的对象是否存在,对message进行不同的设置;最后再将container添加到传入的handler里,调用父类onOpen进行后续处理;
其他方法方法体为空或者只是单纯的返回本类的属性,没有分析的必要
篇幅原因,本周代码分析仅分析至此,下周开始,对common包下的FileTreePanel以及OmTreeProvider类进行分析。