2021SC@SDUSC
软件工程应用与实践——OpenMeetings项目分析(四):
承接上周任务,本周继续进行util包下的BaseWebService的各派生类的源码分析;
CalendarWebService.java
首先分析的是CalendarWebService.java,先贴上源码:
/**
* CalendarService contains methods to create, edit delete calendar meetings
*
* @author sebawagner
*
*/
@Service("calendarWebService")
@WebService(serviceName="org.apache.openmeetings.webservice.CalendarWebService", targetNamespace = TNS)
@Features(features = "org.apache.cxf.ext.logging.LoggingFeature")
@Produces({MediaType.APPLICATION_JSON})
@Path("/calendar")
public class CalendarWebService extends BaseWebService {
private static final Logger log = Red5LoggerFactory.getLogger(CalendarWebService.class, getWebAppRootKey());
private static AppointmentDao getDao() {
return getBean(AppointmentDao.class);
}
/**
* Load appointments by a start / end range for the current SID
*
* @param sid
* The SID of the User. This SID must be marked as Loggedin
* @param start
* start time
* @param end
* end time
*
* @return - list of appointments in range
*/
@GET
@Path("/{start}/{end}")
public List<AppointmentDTO> range(@QueryParam("sid") @WebParam(name="sid") String sid
, @PathParam("start") @WebParam(name="start") Calendar start
, @PathParam("end") @WebParam(name="end") Calendar end
)
{
log.debug("range : startdate - {} , enddate - {}"
, start == null ? "" : start.getTime()
, end == null ? "" : end.getTime());
return performCall(sid, User.Right.Room
, sd -> AppointmentDTO.list(getDao().getInRange(sd.getUserId(), start.getTime(), end.getTime())));
}
/**
* Load appointments by a start / end range for the userId
*
* @param sid
* The SID of the User. This SID must be marked as Loggedin
* @param userid
* the userId the calendar events should be loaded
* @param start
* start time
* @param end
* end time
*
* @return - list of appointments in range
*/
@GET
@Path("/{userid}/{start}/{end}")
public List<AppointmentDTO> rangeForUser(
@QueryParam("sid") @WebParam(name="sid") String sid
, @PathParam("userid") @WebParam(name="userid") long userid
, @PathParam("start") @WebParam(name="start") Calendar start
, @PathParam("end") @WebParam(name="end") Calendar end
)
{
log.debug("rangeForUser : startdate - {} , enddate - {}"
, start == null ? "" : start.getTime()
, end == null ? "" : end.getTime());
return performCall(sid, User.Right.Soap
, sd -> AppointmentDTO.list(getDao().getInRange(userid, start.getTime(), end.getTime())));
}
/**
* Get the next Calendar event for the current user of the SID
*
* @param sid
* The SID of the User. This SID must be marked as Loggedin
* @return - next Calendar event
*/
@GET
@Path("/next")
public AppointmentDTO next(@QueryParam("sid") @WebParam(name="sid") String sid) {
return performCall(sid, User.Right.Room, sd -> {
Appointment a = getDao().getNext(sd.getUserId(), new Date());
return a == null ? null : new AppointmentDTO(a);
});
}
/**
* Get the next Calendar event for userId
*
* @param sid
* The SID of the User. This SID must be marked as Loggedin
* @param userid
* the userId the calendar events should be loaded
*
* @return - next Calendar event
*/
@GET
@Path("/next/{userid}")
public AppointmentDTO nextForUser(@QueryParam("sid") @WebParam(name="sid") String sid, @PathParam("userid") @WebParam(name="userid") long userid) {
return performCall(sid, User.Right.Soap, sd -> {
Appointment a = getDao().getNext(userid, new Date());
return a == null ? null : new AppointmentDTO(a);
});
}
/**
*
* Load a calendar event by its room id
*
* @param sid
* The SID of the User. This SID must be marked as Loggedin
* @param roomid
* id of appointment special room
* @return - calendar event by its room id
*/
@GET
@Path("/room/{roomid}")
public AppointmentDTO getByRoom(@QueryParam("sid") @WebParam(name="sid") String sid, @PathParam("roomid") @WebParam(name="roomid") long roomid) {
return performCall(sid, User.Right.Room, sd -> {
Appointment a = getDao().getByRoom(sd.getUserId(), roomid);
return a == null ? null : new AppointmentDTO(a);
});
}
/**
* Search a calendar event for the current SID
*
* @param sid
* The SID of the User. This SID must be marked as Loggedin
* @param title
* the search string
*
* @return - calendar event list
*/
@GET
@Path("/title/{title}")
public List<AppointmentDTO> getByTitle(@QueryParam("sid") @WebParam(name="sid") String sid, @PathParam("title") @WebParam(name="title") String title) {
return performCall(sid, User.Right.Room, sd -> AppointmentDTO.list(getDao().searchByTitle(sd.getUserId(), title)));
}
/**
* Save an appointment
*
* @param sid
* The SID of the User. This SID must be marked as Loggedin
* @param appointment
* calendar event
*
* @return - appointment saved
*/
@WebMethod
@POST
@Path("/")
public AppointmentDTO save(@QueryParam("sid") @WebParam(name="sid") String sid, @FormParam("appointment") @WebParam(name="appointment") AppointmentDTO appointment) {
//Seems to be create
log.debug("save SID: {}", sid);
UserDao userDao = getUserDao();
return performCall(sid, sd -> {
User u = userDao.get(sd.getUserId());
if (!AuthLevelUtil.hasUserLevel(u.getRights())) {
log.error("save: not authorized");
return false;
}
return AuthLevelUtil.hasWebServiceLevel(u.getRights())
|| appointment.getOwner() == null
|| appointment.getOwner().getId().equals(u.getId());
}, sd -> {
User u = userDao.get(sd.getUserId());
AppointmentDao dao = getDao();
Appointment a = appointment.get(userDao, getFileDao(), dao, u);
if (a.getRoom().getId() != null) {
if (a.getRoom().isAppointment()) {
a.getRoom().setIspublic(false);
} else {
a.setRoom(getRoomDao().get(a.getRoom().getId()));
}
}
return new AppointmentDTO(dao.update(a, u.getId()));
});
}
/**
*
* delete a calendar event
*
* If the given sid is from an Administrator or Web-Service user, the user
* can delete any appointment.
* If the sid is assigned to a regular user, he can only delete appointments
* where he is also the owner/creator of the appointment
*
* @param sid
* an authenticated SID
* @param id
* the id to delete
* @return {@link ServiceResult} with result type
*/
@DELETE
@Path("/{id}")
public ServiceResult delete(@QueryParam("sid") @WebParam(name="sid") String sid, @PathParam("id") @WebParam(name="id") Long id) {
AppointmentDao dao = getDao();
Appointment a = dao.get(id);
return performCall(sid, sd -> {
Set<Right> rights = getRights(sd.getUserId());
if (AuthLevelUtil.hasWebServiceLevel(rights) || AuthLevelUtil.hasAdminLevel(rights)) {
return true;
// fine
}
if (AuthLevelUtil.hasUserLevel(rights) && a.getOwner().getId().equals(sd.getUserId())) {
return true;
}
return false;
}, sd -> {
if (a == null) {
throw new ServiceException("Bad id");
}
dao.delete(a, sd.getUserId());
return new ServiceResult("Deleted", Type.SUCCESS);
});
}
}
根据注释可以看出,CalendarWebService类包含了创建编辑和删除指定日期的会议的方法,因此主要功能是预定会议时日期的添加删除与编辑等操作;先观察IDEA生成的关于此类的结构图,如下:
包括了数个公有方法,一个私有方法,以及一个私有数据log;
getDao方法:返回AppointmentDao(db.dao.caldener下的一个实体类)调用了其父类的getBean方法,传入AppointmentDao的class属性返回一个bean;
range方法:根据方法的注释,这个方法需要三个参数,SID,start,end,返回一个指定范围的AppointmentDTo的列表,首先利用org.slf4j.Logger 的log对象调用debug方法对开始日期以及结束日期进行非空判断,然后调用父类的performcall方法返回一个列表;
rangeForuser方法:需要四个参数,用户的sid(此用户必须是已经登录的),userid数值型的用户标识符,开始时间start以及结束时间end,同样地返回一个AppointmentDTO的列表;这两个方法都用到了Appointment类的一个getInrange方法,因此对这个方法也稍作分析,同样需要用户id,开始时间以及结束时间,利用javax.persistence.EntityManager 的对象进行创建队列,然后按照参数要求将appointment对象创建出来添加到新的一个列表,最后将这个列表返回;
next方法:获取用户id为sid的用户的下一个日期事件,接收一个sid的参数,返回一个calendar event对象即AppointmentDTO类型;
nextForUser方法:参数为sid和userid,分别为当前登录用户的id以及要进行事件加载的人的id,返回的同样是next Calendar event,下一个日期事件
getByRoom方法:此方法作用为按房间ID加载日期事件,接收参数为sid,roomid即要进行会议的房间标号,返回一个AppointmentDTO;
getByTitle方法:搜索当前用户(用户id为sid)的日历事件,接收参数为sid以及title(搜索的标题),返回的是一个AppointmentDTO类型得列表;
save方法:见名知意,保存,此方法用来保存日历事件,接收参数为sid以及appointment对象(AppointmentDTO),返回值为AppointmentDTO;
同时方法内使用了lambda表达式和函数式接口Predicate和Function,在上一周分析时有提到,举个简单的例子,方法中的performcall里的两个参数sd ->{}就是lamda表达式,举个简单的例子,consumer就是一个函数式接口:
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("Java3y");
delete方发:同上,delete方法是用于删除日历事件的 如果给定的 sid 来自管理员或 Web 服务用户,则该用户可以删除任何约会。 如果 sid 分配给普通用户,则他只能删除同时也是约会所有者/创建者的约会,接收参数为经过身份验证的用户sid以及待删除的会议id,返回一个带有结果类型的ServiceResult;
Constants.java
constants的意思是常量,因此本类主要定义一些常量;
先贴上源码:
package org.apache.openmeetings.webservice;
public class Constants {
public static final String TNS = "http://webservice.openmeetings.apache.org/";
public static final String USER_SERVICE_NAME = "org.apache.openmeetings.webservice.UserWebService";
public static final String USER_SERVICE_PORT_NAME = "UserService";
private Constants() {}
}
定义三个静态字符串类型的常量TNS\USER_SERVICE_NAME和USER_SERVICE_PORT_NAME
ErrorWebService.java
先附上源代码:
package org.apache.openmeetings.webservice;
import static org.apache.openmeetings.util.OpenmeetingsVariables.getWebAppRootKey;
import static org.apache.openmeetings.webservice.Constants.TNS;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import org.apache.cxf.feature.Features;
import org.apache.openmeetings.db.dao.label.LabelDao;
import org.apache.openmeetings.db.dto.basic.ServiceResult;
import org.apache.openmeetings.db.dto.basic.ServiceResult.Type;
import org.apache.openmeetings.db.entity.server.Sessiondata;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import org.springframework.stereotype.Service;
/**
*
* The Service contains methods to get localized errors
*
* @author solomax
*
*/
@Service("errorWebService")
@WebService(serviceName="org.apache.openmeetings.webservice.ErrorWebService", targetNamespace = TNS)
@Features(features = "org.apache.cxf.ext.logging.LoggingFeature")
@Produces({MediaType.APPLICATION_JSON})
@Path("/error")
public class ErrorWebService extends BaseWebService {
private static final Logger log = Red5LoggerFactory.getLogger(ErrorWebService.class, getWebAppRootKey());
/**
* loads an Error-Object. If a Method returns a negative Result, its an
* Error-id, it needs a languageId to specify in which language you want to
* display/read the error-message. English has the Language-ID one, for
* different one see the list of languages
*
* @param key
* the error key for ex. `error.unknown`
* @param lang
* The id of the language
*
* @return - error with the code given
*/
@WebMethod
@GET
@Path("/{key}/{lang}")
public ServiceResult get(@WebParam(name="key") @PathParam("key") String key, @WebParam(name="lang") @PathParam("lang") long lang) {
try {
String eValue = LabelDao.getString(key, lang);
return new ServiceResult(eValue, Type.SUCCESS);
} catch (Exception err) {
log.error("[get] ", err);
}
return null;
}
@WebMethod
@POST
@Path("/report/")
public void report(@WebParam(name="sid") @QueryParam("sid") String sid, @WebParam(name="message") @QueryParam("message") String message) {
if (sid != null && message != null) {
Sessiondata sd = check(sid);
if (sd.getId() != null) {
log.error("[CLIENT MESSAGE] " + message);
}
}
}
}
ErrorWebService类继承自basewebservice,包含获取本地化错误的方法;代码量较少,从idea观察其结构,如图:
包含一个成员变量log和之前的类一样,用了工厂模式,log是由Red5LoggerFactory对象调勇getLogger方法生产的;同时包含了两个共有方法get以及report;
get方法:加载一个错误对象。 如果一个方法返回一个负结果,它是一个错误 ID,它需要一个 languageId 来指定你想用哪种语言显示/读取错误消息。 英语有 Language-ID 之一,不同的语言可以根据语言列表指定;接收参数为key,错误的键,以及lang是语言的id标识符;返回一个ServiceResult的类型得代码错误;
report方法:检测有没有出现异常,检测到异常,就调用log对象的error方法将异常的message发送;
FileWebService.java
package org.apache.openmeetings.webservice;
@Service("fileWebService")
@WebService(serviceName="org.apache.openmeetings.webservice.FileWebService", targetNamespace = TNS)
@Features(features = "org.apache.cxf.ext.logging.LoggingFeature")
@Produces({MediaType.APPLICATION_JSON})
@Path("/file")
public class FileWebService extends BaseWebService {
private static final Logger log = Red5LoggerFactory.getLogger(FileWebService.class, getWebAppRootKey());
/**
* deletes files or folders based on it id
*
* @param sid
* The SID of the User. This SID must be marked as logged in
* @param id
* the id of the file or folder
* @return {@link ServiceResult} with result type
*/
@DELETE
@Path("/{id}")
public ServiceResult delete(@QueryParam("sid") @WebParam(name="sid") String sid, @PathParam("id") @WebParam(name="id") Long id) {
FileItemDao dao = getFileDao();
FileItem f = dao.get(id);
return performCall(sid, sd -> {
Long userId = sd.getUserId();
Set<Right> rights = getRights(userId);
return AuthLevelUtil.hasWebServiceLevel(rights)
|| (AuthLevelUtil.hasUserLevel(rights) && userId.equals(f.getOwnerId()));
}
, sd -> {
if (f == null) {
return new ServiceResult("Bad id", Type.ERROR);
}
dao.delete(f);
return new ServiceResult("Deleted", Type.SUCCESS);
});
}
/**
*
* deletes a file by its external Id and type
*
* @param sid
* The SID of the User. This SID must be marked as logged in
* @param externalId
* the od of the file or folder
* @param externalType
* the externalType
* @return {@link ServiceResult} with result type
*/
@DELETE
@Path("/{externaltype}/{externalid}")
public ServiceResult deleteExternal(
@WebParam(name="sid") @QueryParam("sid") String sid
, @WebParam(name="externaltype") @PathParam("externaltype") String externalType
, @WebParam(name="externalid") @PathParam("externalid") String externalId
)
{
return performCall(sid, User.Right.Soap, sd -> {
FileItemDao dao = getFileDao();
FileItem f = dao.get(externalId, externalType);
dao.delete(f);
return new ServiceResult("Deleted", Type.SUCCESS);
});
}
/**
* to add a folder to the private drive, set parentId = 0 and isOwner to 1/true and
* externalUserId/externalUserType to a valid user
*
* @param sid
* The SID of the User. This SID must be marked as logged in
* @param file
* the The file to be added
* @param stream
* the The file to be added
* @return - Object created
*/
@WebMethod
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/")
public FileItemDTO add(@WebParam(name="sid") @QueryParam("sid") String sid
, @Multipart(value = "file", type = MediaType.APPLICATION_JSON) @WebParam(name="file") FileItemDTO file
, @Multipart(value = "stream", type = MediaType.APPLICATION_OCTET_STREAM, required = false) @WebParam(name="stream") InputStream stream
)
{
return performCall(sid, User.Right.Soap, sd -> {
FileItem f = file == null ? null : file.get();
if (f == null || f.getId() != null) {
throw new ServiceException("Bad id");
}
f.setInsertedBy(sd.getUserId());
if (stream != null) {
try {
ProcessResultList result = getBean(FileProcessor.class).processFile(f, stream);
if (result.hasError()) {
throw new ServiceException(result.getLogMessage());
}
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
} else {
f = getFileDao().update(f);
}
return new FileItemDTO(f);
});
}
/**
* Get all files by external type
*
* @param sid
* The SID of the User. This SID must be marked as logged in
* @param externalType
* External type for file listing
* @return - the list of file for given external type
*/
@WebMethod
@GET
@Path("/{externaltype}")
public List<FileItemDTO> getAllExternal(@WebParam(name="sid") @QueryParam("sid") String sid
, @WebParam(name="externaltype") @PathParam("externaltype") String externalType
)
{
log.debug("getAllExternal::externalType {}", externalType);
return performCall(sid, User.Right.Soap, sd -> {
FileItemDao dao = getFileDao();
return FileItemDTO.list(dao.getExternal(externalType));
});
}
/**
* Get a File Explorer Object by a given Room
*
* @param sid
* The SID of the User. This SID must be marked as logged in
* @param roomId
* Room Id
* @return - File Explorer Object by a given Room
*/
@WebMethod
@GET
@Path("/room/{id}")
public FileExplorerObject getRoom(@WebParam(name="sid") @QueryParam("sid") String sid
, @WebParam(name="id") @PathParam("id") long roomId
)
{
log.debug("getRoom::roomId {}", roomId);
return performCall(sid, User.Right.Soap, sd -> {
FileItemDao dao = getFileDao();
FileExplorerObject fileExplorerObject = new FileExplorerObject();
// Home File List
List<FileItem> fList = dao.getByOwner(sd.getUserId());
fileExplorerObject.setUser(fList, dao.getSize(fList));
// Public File List
List<FileItem> rList = dao.getByRoom(roomId);
fileExplorerObject.setRoom(rList, dao.getSize(rList));
return fileExplorerObject;
});
}
/**
*
* Get list of {@link FileItemDTO} by parent
*
* @param sid
* SID The SID of the User. This SID must be marked as logged in
* @param parentId
* the parent folder id
* @param roomId
* the room id
* @return - list of file explorer items
*/
@WebMethod
@GET
@Path("/room/{id}/{parent}")
public List<FileItemDTO> getRoomByParent(@WebParam(name="sid") @QueryParam("sid") String sid
, @WebParam(name="id") @PathParam("id") long roomId
, @WebParam(name="parent") @PathParam("parent") long parentId
)
{
log.debug("getRoomByParent {}", parentId);
return performCall(sid, User.Right.Room, sd -> {
FileItemDao dao = getFileDao();
List<FileItem> list;
if (parentId < 0) {
if (parentId == -1) {
list = dao.getByOwner(sd.getUserId());
} else {
list = dao.getByRoom(roomId);
}
} else {
list = dao.getByParent(parentId);
}
return FileItemDTO.list(list);
});
}
/**
*
* update a file or folder name
*
* @param sid
* SID The SID of the User. This SID must be marked as logged in
* @param id
* file or folder id
* @param name
* new file or folder name
* @return - resulting file object
*/
@WebMethod
@POST
@Path("/rename/{id}/{name}")
public FileItemDTO rename(@WebParam(name="sid") @QueryParam("sid") String sid
, @WebParam(name="id") @PathParam("id") long id
, @WebParam(name="name") @PathParam("name") String name)
{
log.debug("rename {}", id);
return performCall(sid, User.Right.Soap, sd -> {
FileItem f = getFileDao().rename(id, name);
return f == null ? null : new FileItemDTO(f);
});
}
/**
* move a file or folder
*
* @param sid
* SID The SID of the User. This SID must be marked as logged in
* @param id
* current file or folder id to be moved
* @param roomId
* room this file need to be moved
* @param parentId
* new parent folder id
* @return - resulting file object
*/
@WebMethod
@POST
@Path("/move/{roomid}/{id}/{parentid}")
public FileItemDTO move(@WebParam(name="sid") @QueryParam("sid") String sid
, @WebParam(name="id") @PathParam("id") long id
, @WebParam(name="roomid") @PathParam("roomid") long roomId
, @WebParam(name="parentid") @PathParam("parentid") long parentId)
{
log.debug("move {}", id);
return performCall(sid, User.Right.Soap, sd -> {
FileItem f = getFileDao().move(id, parentId, sd.getUserId(), roomId);
return f == null ? null : new FileItemDTO(f);
});
}
}
同上,先查看类的结构:
FileWebService.java继承自basewebservice包含将文件导入和上传到会议室的文件部分和任何用户的个人驱动器的方法;主要包括8个公有方法和一个成员变量;
delete方法:根据id删除已经上传的文件或者文件夹,接收参数为sid登录用户的标识以及欲删除的文件的id,返回的结果类型为ServiceResult;
deleteExternal方法:按外部 ID 和类型删除文件,接收参数为:sid – 用户的 SID。 此 SID 必须标记为已登录,externalType – 外部类型,externalId – 文件或文件夹的 od,返回值是带有结果类型的ServiceResult
add方法:此方法主要目的是将文件添加到会议中,要将文件夹添加到私有驱动器,请将 parentId = 0 和 isOwner 设置为 1/true 并将 externalUserId/externalUserType 设置为有效用户,接收参数为sid – 用户的 SID, 此 SID 必须标记为已登录,file – 要添加的文件,stream – 要添加的文件(流类型),返回一个FileItemDTO;
getAllExternal方法:按外部类型获取所有文件,接收形参:sid – 用户的 SID。 此 SID 必须标记为已登录,externalType – 文件列表的外部类型返回值:给定外部类型的文件列表‘;
getRoom方法:此方法主要目的是通过给定房间获取文件资源管理器对象,接收形参:sid – 用户的 SID。 此 SID 必须标记为已登录,roomId – 房间 ID,返回值:给定房间的文件资源管理器对象
getRoomByParent方法:此方法主要目的是通过父获取FileItemDTO列表,接收形参:sid – SID 用户的 SID。 此 SID 必须标记为已登录,roomId – 房间 ID,parentId – 父文件夹 ID返回值:文件资源管理器项目列表
rename方法:此方法主要目的是更新文件或文件夹名称,接收形参:sid – SID 用户的 SID。 此 SID 必须标记为已登录,id – 文件或文件夹 ID,名称 - 新文件或文件夹名称返回值:结果文件对象
move方法:此方法主要目的是移动文件或文件夹,接收形参:sid – SID 用户的 SID。 此 SID 必须标记为已登录,id – 要移动的当前文件或文件夹 ID,roomId – 这个文件需要移动的房间,parentId – 新的父文件夹 ID,返回值:结果文件对象
总结:这几个webservice类均继承自basewebservice,作用为通过调用被注入的多个dao对象进行对不同事务的控制与管理,完成各种操作;这周没能分析完全util包下的所有派生类,下周先把剩下的派生类分析完,搞清楚每个类负责控制管理什么服务,然后根据情况开始web包下的分析。