net.gdface.facelog.DaoManagement Maven / Gradle / Ivy
package net.gdface.facelog;
import static com.google.common.base.Preconditions.*;
import static gu.sql2java.Managers.*;
import static net.gdface.facelog.FeatureConfig.*;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import gu.sql2java.BaseBean;
import gu.sql2java.RowMetaData;
import gu.sql2java.TableManager;
import gu.sql2java.exception.ObjectRetrievalException;
import gu.sql2java.exception.RuntimeDaoException;
import net.gdface.facelog.db.Constant;
import net.gdface.facelog.db.DeviceBean;
import net.gdface.facelog.db.DeviceGroupBean;
import net.gdface.facelog.db.ErrorLogBean;
import net.gdface.facelog.db.FaceBean;
import net.gdface.facelog.db.FeatureBean;
import net.gdface.facelog.db.IDeviceGroupManager;
import net.gdface.facelog.db.IPermitManager;
import net.gdface.facelog.db.IPersonGroupManager;
import net.gdface.facelog.db.ImageBean;
import net.gdface.facelog.db.LogBean;
import net.gdface.facelog.db.PermitBean;
import net.gdface.facelog.db.PersonBean;
import net.gdface.facelog.db.PersonGroupBean;
import net.gdface.facelog.db.StoreBean;
import net.gdface.image.LazyImage;
import net.gdface.image.NotImageException;
import net.gdface.image.UnsupportedFormatException;
import net.gdface.utils.Assert;
import net.gdface.utils.BinaryUtils;
import net.gdface.utils.Judge;
import net.gdface.utils.MiscellaneousUtils;
/**
* 数据库操作扩展
* @author guyadong
*
*/
public class DaoManagement extends BaseDao implements ServiceConstant,Constant{
private final CryptographGenerator cg;
public DaoManagement(CryptographGenerator cg) {
this.cg = checkNotNull(cg,"cg is null");
}
public DaoManagement() {
this(CryptographBySalt.INSTANCE);
}
public CryptographGenerator getCryptographGenerator() {
return cg;
}
/** 检查姓名是否有效,不允许使用保留字{@code root} ,无效抛出{@link IllegalArgumentException} 异常 */
protected static void checkPersonName(PersonBean personBean){
checkArgument(null == personBean || !ROOT_NAME.equals(personBean.getName()),
"INVALID person name:%s, reserved word",ROOT_NAME);
}
/**
* 增加人员姓名检查,参见 {@link #checkPersonName(PersonBean)}
* 增加 password密文更新
* @throws IllegalStateException {@code password}不是有效的MD5字符串
*/
@Override
protected PersonBean daoSavePerson(PersonBean personBean) throws RuntimeDaoException {
checkPersonName(personBean);
if(personBean.checkPasswordModified()){
String password = personBean.getPassword();
if(null != password){
checkState(BinaryUtils.validMd5(password),"password field must be MD5 string(32 char,lower case)");
// 重新生成password加盐密文
personBean.setPassword(cg.cryptograph(password, true));
}
}
return super.daoSavePerson(personBean);
}
protected static StoreBean makeStoreBean(ByteBuffer imageBytes,String md5,String encodeing){
if(Judge.isEmpty(imageBytes)){
return null;
}
if(null == md5){
md5 = BinaryUtils.getMD5String(imageBytes);
}
StoreBean storeBean = new StoreBean();
storeBean.setData(imageBytes);
storeBean.setMd5(md5);
if(!Strings.isNullOrEmpty(encodeing)){
storeBean.setEncoding(encodeing);
}
return storeBean;
}
/////////////////////PERMIT////////////////////
/**
* 获取人员组通行权限
* 返回{@code personGroupId}指定的人员组在{@code deviceGroupId}指定的设备组上是否允许通行,
* 本方法会对{@code deviceGroupId}的子结点向下递归:
* {@code personGroupId } 及其子结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true,
* 输入参数为{@code null}或找不到指定的记录则返回false
* @param deviceGroupId
* @param personGroupId
* @return 允许通行返回指定的{@link PermitBean}记录,否则返回{@code null}
*/
private PermitBean daoGetPermitOnPersonGroup(Integer deviceGroupId, final Integer personGroupId) throws RuntimeDaoException {
DeviceGroupBean deviceGroup;
if(null == deviceGroupId
|| null == personGroupId
|| null == (deviceGroup = daoGetDeviceGroup(deviceGroupId))){
return null;
}
for(DeviceGroupBean group : daoChildListByParentForDeviceGroup(deviceGroup)){
PermitBean permit = daoGetPermit(group.getId(), personGroupId);
if(permit != null){
return permit;
}
}
return null;
}
/**
* 获取人员组通行权限
* 返回{@code personGroupId}指定的人员组在{@code deviceGroupId}指定的设备组上是否允许通行,
* 本方法会对{@code personGroupId}的父结点向上回溯:
* {@code personGroupId } 及其父结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true,
* 输入参数为{@code null}或找不到指定的记录则返回false
* @param deviceGroupId
* @param personGroupId
* @return 允许通行返回指定的{@link PermitBean}记录,否则返回{@code null}
*/
protected PermitBean daoGetGroupPermitOnDeviceGroup(final Integer deviceGroupId,Integer personGroupId){
PersonGroupBean personGroup;
if(null == deviceGroupId
|| null == personGroupId
|| null == (personGroup = daoGetPersonGroup(personGroupId))){
return null;
}
List personGroupList = daoListOfParentForPersonGroup(personGroup);
// first is self
Collections.reverse(personGroupList);
for(PersonGroupBean group : personGroupList){
PermitBean permit = daoGetPermitOnPersonGroup(deviceGroupId,group.getId());
if(permit != null){
return permit;
}
}
return null;
}
/**
* 获取人员组通行权限
* 返回{@code personGroupId}指定的人员组在{@code deviceId}设备上是否允许通行,
* 本方法会对{@code personGroupId}的父结点向上回溯:
* {@code personGroupId } 及其父结点,任何一个在permit表存在与{@code deviceId}所属设备级的关联记录中就返回true,
* 输入参数为{@code null}或找不到指定的记录则返回false
* @param deviceId
* @param personGroupId
* @return 允许通行返回指定的{@link PermitBean}记录,否则返回{@code null}
* @see #daoGetGroupPermitOnDeviceGroup(Integer, Integer)
*/
protected PermitBean daoGetGroupPermit(Integer deviceId,Integer personGroupId){
DeviceBean device;
if(null == deviceId || null ==(device = daoGetDevice(deviceId))){
return null;
}
return daoGetGroupPermitOnDeviceGroup(device.getGroupId(),personGroupId);
}
protected PermitBean daoGetPersonPermit(Integer deviceId,Integer personId){
PersonBean person;
if( null == personId || null == (person = daoGetPerson(personId))){
return null;
}
return daoGetGroupPermit(deviceId,person.getGroupId());
}
/**
* 判断指定用户是否可以在指定设备上通行
* @param personId 用户ID
* @param deviceId 设备ID
* @return 允许通行返回{@code true},否则返回{@code false}
*/
protected boolean daoIsPermit(int personId,int deviceId){
Set features = daoGetPersonsPermittedOnDevice(deviceId, false, null, null);
return Collections2.transform(features, daoCastPersonToPk).contains(personId);
}
protected List daoGetGroupPermit(final Integer deviceId,List personGroupIdList){
if(null == deviceId || null == personGroupIdList){
return Collections.emptyList();
}
return Lists.newArrayList(Lists.transform(personGroupIdList, new Function(){
@Override
public PermitBean apply(Integer input) {
return daoGetGroupPermit(deviceId,input);
}}));
}
protected List daoGetPermit(final Integer deviceId,List personIdList){
if(null == deviceId || null == personIdList){
return Collections.emptyList();
}
return Lists.newArrayList(Lists.transform(personIdList, new Function(){
@Override
public PermitBean apply(Integer input) {
return daoGetPersonPermit(deviceId,input);
}}));
}
/**
* 从permit表返回允许在{@code deviceGroupId}指定的设备组通过的所有人员组({@link PersonGroupBean})对象的id
* @param deviceGroupId 为{@code null}返回空表
* @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
* @return 人员组id列表
*/
protected List daoGetPersonGroupsPermittedBy(Integer deviceGroupId,boolean ignoreSchedule){
if(deviceGroupId == null){
return Collections.emptyList();
}
Set permittedGroups = Sets.newHashSet();
DateTimeJsonFilter shedule = new DateTimeJsonFilter();
final Date date = new Date();
for(PermitBean permit : daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId)){
if(ignoreSchedule || shedule.apply(date,permit.getSchedule())){
permittedGroups.addAll(daoChildListByParentForPersonGroup(permit.getPersonGroupId()));
}
}
return Lists.newArrayList(Iterables.transform(permittedGroups, daoCastPersonGroupToPk));
}
/**
* 从permit表返回允许在{@code deviceGroupId}指定的设备组通过的所有人员组({@link PersonGroupBean})对象的id,
* 忽略时间过滤器(fl_permit.schedule字段)的限制
* @param deviceGroupId 为{@code null}返回空表
* @return 人员组id列表
*/
protected List daoGetPersonGroupsPermittedBy(Integer deviceGroupId){
if(deviceGroupId == null){
return Collections.emptyList();
}
List permits = daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId);
return Lists.transform(permits, daoCastPermitToPersonGroupId);
}
/**
* 从permit表返回允许{@code personGroupId}指定的人员组通过的所有设备组({@link DeviceGroupBean})对象的id
* @param personGroupId 为{@code null}返回空表
* @return 设备组id列表
*/
protected List daoGetDeviceGroupsPermittedBy(Integer personGroupId){
if(personGroupId == null){
return Collections.emptyList();
}
List permits = daoGetPermitBeansByPersonGroupIdOnPersonGroup(personGroupId);
return Lists.transform(permits, daoCastPermitToDeviceGroupId);
}
/**
* 从permit表返回允许在{@code personGroupId}指定的人员组通过的所有设备组({@link DeviceGroupBean})的id
* 不排序,不包含重复id
* @param personGroupId 为{@code null}返回空表
* @return 设备组id列表
*/
protected List daoGetDeviceGroupsPermit(Integer personGroupId){
if(personGroupId == null){
return Collections.emptyList();
}
PermitBean template = PermitBean.builder().personGroupId(personGroupId).build();
List permits = daoLoadPermitUsingTemplate(template, 1, -1);
HashSet groups = Sets.newHashSet();
for (PermitBean bean : permits) {
groups.addAll(Lists.transform(daoListOfParentForDeviceGroup(bean.getDeviceGroupId()),daoCastDeviceGroupToPk));
}
return Lists.newArrayList(groups);
}
/**
* 从permit表删除指定{@code personGroupId}指定人员组的在所有设备上的通行权限
* @param personGroupId 为{@code null}返回0
* @return 删除的记录条数
*/
protected int daoDeletePersonGroupPermit(Integer personGroupId) {
if(personGroupId == null){
return 0;
}
PermitBean template = PermitBean.builder().personGroupId(personGroupId).build();
return instanceOf(IPermitManager.class).deleteUsingTemplate(template);
}
/**
* 从permit表删除指定{@code deviceGroupId}指定设备组上的人员通行权限
* @param deviceGroupId 为{@code null}返回0
* @return 删除的记录条数
*/
protected int daoDeleteGroupPermitOnDeviceGroup(Integer deviceGroupId) {
if(deviceGroupId == null){
return 0;
}
PermitBean template = PermitBean.builder().deviceGroupId(deviceGroupId).build();
return instanceOf(IPermitManager.class).deleteUsingTemplate(template);
}
////////////////////////////////////////////
/**
* 创建{@link ImageBean}对象,填充图像基本信息,同时创建对应的{@link StoreBean}对象
* @param imageBytes
* @param md5
* @return 返回 {@link ImageBean}和{@link StoreBean}对象
* @throws NotImageException
* @throws UnsupportedFormatException
*/
protected static Pair makeImageBean(ByteBuffer imageBytes,String md5) throws NotImageException, UnsupportedFormatException{
if(Judge.isEmpty(imageBytes)){
return null;
}
LazyImage image = LazyImage.create(imageBytes);
if(null == md5){
md5 = BinaryUtils.getMD5String(imageBytes);
}
ImageBean imageBean = new ImageBean();
imageBean.setMd5(md5);
imageBean.setWidth(image.getWidth());
imageBean.setHeight(image.getHeight());
imageBean.setFormat(image.getSuffix());
StoreBean storeBean = makeStoreBean(imageBytes, md5, null);
return Pair.of(imageBean, storeBean);
}
protected ImageBean daoAddImage(ByteBuffer imageBytes,DeviceBean refFlDevicebyDeviceId
, Collection impFlFacebyImgMd5 , Collection impFlPersonbyImageMd5) throws DuplicateRecordException{
if(Judge.isEmpty(imageBytes)){
return null;
}
String md5 = BinaryUtils.getMD5String(imageBytes);
daoCheckDuplicateImage(md5);
Pair pair;
try {
pair = makeImageBean(imageBytes,md5);
} catch (Exception e) {
throw new RuntimeException(e);
}
daoAddStore(pair.getRight());
if(null != impFlFacebyImgMd5){
pair.getLeft().setFaceNum(impFlFacebyImgMd5.size());
}
return daoAddImage(pair.getLeft(), refFlDevicebyDeviceId, impFlFacebyImgMd5,null, impFlPersonbyImageMd5);
}
/**
* (递归)删除imageMd5指定图像及其缩略图
* @param imageMd5
* @return 返回删除的记录条数(1),如果记录不存在返回0
*/
@Override
protected int daoDeleteImage(String imageMd5){
if(Strings.isNullOrEmpty(imageMd5)){
return 0;
}
daoDeleteStore(imageMd5);
ImageBean imageBean = daoGetImage(imageMd5);
if(null == imageBean){return 0;}
String thumbMd5 = imageBean.getThumbMd5();
if( !Strings.isNullOrEmpty(thumbMd5)&& !imageBean.getMd5().equals(thumbMd5)){
daoDeleteImage(thumbMd5);
}
return super.daoDeleteImage(imageMd5);
}
/**
* 根据配置定义的SDK算法参数,从特征数据中剥离出MD5计算范围的特征数据
* @param featureVersion
* @param feature
* @return MD5计算范围的特征数据
*/
private static byte[] stripFeature(String featureVersion,ByteBuffer feature){
int md5Off = FEATURE_CONFIG.md5OffsetOf(featureVersion);
int md5Len = FEATURE_CONFIG.md5LengthOf(featureVersion);
byte[] featureBytes = BinaryUtils.getBytesInBuffer(feature);
if(md5Len >= 0 && md5Len > 0 && (md5Off + md5Len) != featureBytes.length){
// 检查参数有效性
checkArgument((md5Off + md5Len) < featureBytes.length,
"INVALID md5 range definition off:%s,len:%s for %s(feture size %s)", md5Off, md5Len,featureVersion,featureBytes.length);
return Arrays.copyOfRange(featureBytes, md5Off, md5Off + md5Len);
}
return featureBytes;
}
protected FeatureBean daoMakeFeature(ByteBuffer feature, String featureVersion){
Assert.notEmpty(feature, "feature");
// featureVersion不可为空
checkArgument(!Strings.isNullOrEmpty(featureVersion),"featureVersion is null or empty");
// featureVersion内容只允许字母,数字,-,.,_符号
checkArgument(featureVersion.matches(SDK_VERSION_REGEX), "invalid sdk version format");
byte[] stripedFeature = stripFeature(featureVersion,feature);
String md5 = BinaryUtils.getMD5String(stripedFeature);
return FeatureBean.builder()
.md5(md5)
.feature(feature)
.version(featureVersion)
.build();
}
protected byte[] daoGetFeatureBytes(String md5, boolean truncation) throws IOException{
FeatureBean featureBean = daoGetFeature(md5);
if(featureBean == null){
return null;
}
if(!truncation){
return BinaryUtils.getBytes(featureBean.getFeature());
}
return stripFeature(featureBean.getVersion(),featureBean.getFeature());
}
protected List daoGetFeatureBytes(List md5List, boolean truncation) throws IOException{
List features = Lists.newLinkedList();
if(md5List != null){
for(String md5:md5List){
byte[] feature = daoGetFeatureBytes(md5,truncation);
features.add(feature == null ? new byte[0] : feature) ;
}
}
return features;
}
/**
* 返回 persionId 关联的指定SDK的人脸特征记录
* @param personId 人员id(fl_person.id)
* @param featureVersion 算法(SDK)版本号
* @return 返回 fl_feature.md5 列表
*/
protected List daoGetFeaturesByPersonIdAndSdkVersion(int personId,String featureVersion) {
FeatureBean tmpl = FeatureBean.builder().personId(personId).version(featureVersion).build();
return daoLoadFeatureUsingTemplate(tmpl, 1, -1);
}
/**
* (递归)返回在指定设备上允许通行的所有人员记录
* @param output 输出的用户对象列表,过滤所有有效期失效的用户
* @param deviceId 设备ID
* @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
* @param excludePersonIds 要排除的人员记录id,可为{@code null}
* @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录
*/
private void
getPersonsPermittedOnDeviceGroup(Setoutput,
int deviceGroupId,
boolean ignoreSchedule,
List excludePersonIds,
final Long timestamp) {
Set permittedGroups = Sets.newHashSet();
DateTimeJsonFilter shedule = new DateTimeJsonFilter();
final Date date = new Date();
for(PermitBean permit : daoGetPermitBeansByDeviceGroupIdOnDeviceGroup(deviceGroupId)){
if(ignoreSchedule || shedule.apply(date,permit.getSchedule())){
permittedGroups.addAll(daoChildListByParentForPersonGroup(permit.getPersonGroupId()));
}
}
Set persons = Sets.newHashSet();
for(PersonGroupBean group : permittedGroups){
persons.addAll(daoGetPersonsOfGroup(group.getId()));
}
final Set excludeIds = Sets.newHashSet(MoreObjects.firstNonNull(excludePersonIds, Collections.emptySet()));
output.addAll( Sets.filter(persons,
new Predicate() {
@Override
public boolean apply(PersonBean input) {
Date expiryDate = input.getExpiryDate();
return ! excludeIds.contains(input.getId()) /** 过滤所有有效期失效的用户 */
&& (timestamp == null || input.getUpdateTime().getTime() > timestamp)
&& (null== expiryDate ? true : expiryDate.after(date));
}
}));
for(DeviceGroupBean group:daoChildListByParentForDeviceGroup(deviceGroupId)){
if(group.getId() != deviceGroupId){
// exclude self
getPersonsPermittedOnDeviceGroup(output,group.getId(),ignoreSchedule,excludePersonIds,timestamp);
}
}
}
/**
* 返回在指定设备上允许通行的所有人员记录
* @param deviceId 设备ID
* @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
* @param excludePersonIds 要排除的人员记录id,可为{@code null}
* @param timestamp 不为{@code null}时返回fl_person.expiry_date大于指定时间戳的所有fl_person记录
* @return 返回的用户对象列表中,过滤所有有效期失效的用户
*/
protected Set
daoGetPersonsPermittedOnDevice(int deviceId, boolean ignoreSchedule, List excludePersonIds, final Long timestamp) {
Set output = new HashSet<>();
DeviceBean deviceBean = daoGetDeviceChecked(deviceId);
getPersonsPermittedOnDeviceGroup(output,deviceBean.getGroupId(),ignoreSchedule,excludePersonIds,timestamp);
return output;
}
/**
* 返回在指定设备上允许通行的所有特征记录
* @param deviceId 设备ID
* @param ignoreSchedule 是否忽略时间过滤器(fl_permit.schedule字段)的限制
* @param sdkVersion 特征版本号
* @param excludeFeatureIds 要排除的特征记录id(MD5),可为{@code null}
* @param timestamp 不为{@code null}时返回大于指定时间戳的所有fl_feature记录
* @return 特征记录集合
*/
protected Set
daoGetFeaturesPermittedOnDevice(int deviceId,boolean ignoreSchedule, String sdkVersion,final Collection excludeFeatureIds,
final Long timestamp) {
checkArgument(!Strings.isNullOrEmpty(sdkVersion),"sdkVersion is null or empty");
Set features = Sets.newHashSet();
for(PersonBean person:daoGetPersonsPermittedOnDevice(deviceId, ignoreSchedule, null, null)){
features.addAll(daoGetFeaturesByPersonIdAndSdkVersion(person.getId(),sdkVersion));
}
return Sets.filter(features,new Predicate() {
Set excludeIds = Sets.newHashSet(MoreObjects.firstNonNull(excludeFeatureIds, Collections.emptySet()));
@Override
public boolean apply(FeatureBean input) {
return ! excludeIds.contains(input.getMd5()) && (timestamp == null || input.getUpdateTime().getTime() > timestamp);
}
});
}
/**
* 添加人脸特征数据到数据库
* 如果用户的特征记录数量超过限制,且没有开启自动更新机制则抛出异常,
* 如果已有特征数量超过限制,且开启了自动特征更新机制则删除最老的记录,确保记录总数不超过限制
* @param feature 人脸特征数据
* @param featureVersion SDK版本号
* @param refPersonByPersonId 所属的人员记录
* @param impFaceByFeatureMd5 关联的人脸信息记录
* @return 特征记录对象
* @throws DuplicateRecordException
*/
protected FeatureBean daoAddFeature(ByteBuffer feature,String featureVersion,
PersonBean refPersonByPersonId, Collection impFaceByFeatureMd5) throws DuplicateRecordException{
FEATURE_CONFIG.checkSdkVersion(featureVersion);
boolean removeOld = false;
List features = null;
if(null != refPersonByPersonId && refPersonByPersonId.getId() != null){
Integer personId = refPersonByPersonId.getId();
features = daoGetFeaturesByPersonIdAndSdkVersion(personId,featureVersion);
int count = features.size();
int limitCount = FEATURE_CONFIG.getFeatureLimitPerPerson(featureVersion);
// 如果用户的特征记录数量超过限制,且没有开启自动更新机制则抛出异常
checkState(count < limitCount || FEATURE_CONFIG.featureAutoUpdateEnabled(),
"person(id=%s)'s %s feature count exceed max limit(%s)",personId,featureVersion,limitCount);
// 如果已有特征数量超过限制,且开启了自动特征更新机制则删除最老的记录
if(count >= limitCount && FEATURE_CONFIG.featureAutoUpdateEnabled()){
removeOld = true;
}
}
FeatureBean newFeature = daoAddFeature(daoMakeFeature(feature, featureVersion), refPersonByPersonId, impFaceByFeatureMd5, null);
// 放在成功添加记录之后再执行删除,以防止因为添加特征抛出异常而导致发送错误的通知消息
if(removeOld){
int limitCount = FEATURE_CONFIG.getFeatureLimitPerPerson(featureVersion);
// 以update_time字段排序,删除最旧的记录
Collections.sort(features, RowMetaData.getMetaData(FeatureBean.class).comparatorOf(FL_FEATURE_ID_UPDATE_TIME,/** 降序 */true));
for(int i = limitCount-1;i < features.size();++i){
daoDeleteFeature(features.get(i).getMd5(), true);
logger.info("AUTOUPDATE:remove feature [{}] and associated image",features.get(i).getMd5());
}
}
return newFeature;
}
protected FeatureBean daoAddFeature(ByteBuffer feature,String featureVersion,PersonBean personBean,Map faceInfo, DeviceBean deviceBean) throws DuplicateRecordException{
if(null != faceInfo){
for(Entry entry:faceInfo.entrySet()){
ByteBuffer imageBytes = entry.getKey();
FaceBean faceBean = entry.getValue();
daoAddImage(imageBytes, deviceBean, Arrays.asList(faceBean), null);
}
}
return daoAddFeature(feature, featureVersion, personBean, null == faceInfo?null:faceInfo.values());
}
/**
* 删除指定的特征
* @param featureMd5
* @param deleteImage 为{@code true}则删除关联的 image记录(如果该图像还关联其他特征则不删除)
* @return 返回删除的特征记录关联的图像(image)记录的MD5
*/
protected List daoDeleteFeature(final String featureMd5,boolean deleteImage){
List imageKeys = daoGetImageKeysImportedByFeatureMd5(featureMd5);
if(deleteImage){
for(Iterator itor = imageKeys.iterator();itor.hasNext();){
String md5 = itor.next();
// 如果该图像还关联其他特征则不删除
if(!Iterables.tryFind(daoGetFaceBeansByImageMd5OnImage(md5), new Predicate(){
@Override
public boolean apply(FaceBean input) {
return !featureMd5.equals(input.getFeatureMd5());
}}).isPresent()){
daoDeleteImage(md5);
itor.remove();
}
}
}else{
daoDeleteFaceBeansByFeatureMd5OnFeature(featureMd5);
}
daoDeleteFeature(featureMd5);
return imageKeys;
}
protected List daoDeleteFeatureChecked(final String featureMd5,boolean deleteImage){
if(!Strings.isNullOrEmpty(featureMd5)){
checkArgument(daoExistsFeature(featureMd5),"INVALID feature id %s",featureMd5);
return daoDeleteFeature(featureMd5,true);
}
return Collections.emptyList();
}
protected int daoDeleteAllFeaturesByPersonId(Integer personId,boolean deleteImage){
int count = 0;
for(FeatureBean featureBean: daoGetFeatureBeansByPersonIdOnPerson(personId)){
daoDeleteFeature(featureBean.getMd5(),deleteImage);
++count;
}
return count;
}
protected Integer daoGetDeviceIdOfFeature(String featureMd5){
for(String imageMd5: daoGetImageKeysImportedByFeatureMd5(featureMd5)){
ImageBean imageBean = daoGetImage(imageMd5);
if(null !=imageBean){
return imageBean.getDeviceId();
}
}
return null;
}
protected PersonBean daoSetRefPersonOfFeature(String featureMd5,Integer personId){
PersonBean personBean = daoGetPerson(personId);
FeatureBean featureBean = daoGetFeature(featureMd5);
return (null == personBean || null == featureBean)
? null
: daoSetReferencedByPersonIdOnFeature(featureBean, personBean);
}
protected List daoGetImageKeysImportedByFeatureMd5(String featureMd5){
return Lists.transform(daoGetFaceBeansByFeatureMd5OnFeature(featureMd5), this.daoCastFaceToImageMd5);
}
protected PersonBean daoReplaceFeature(Integer personId,String featureMd5,boolean deleteImage){
daoDeleteAllFeaturesByPersonId(personId, deleteImage);
return daoSetRefPersonOfFeature(featureMd5,personId);
}
protected int daoSavePerson(Map persons) throws DuplicateRecordException {
if(null == persons ){
return 0;
}
int count = 0;
PersonBean personBean ;
for(Entry entry:persons.entrySet()){
personBean = daoSavePerson(entry.getValue(),daoAddImage(entry.getKey(),null,null,null),null);
if(null != personBean){++count;}
}
return count;
}
/**
* 保存人员记录
* 如果提供了新的身份照片({@code idPhotoBean}不为{@code null}),则删除旧照片用新照片代替
* @param personBean
* @param idPhotoBean 新的身份照片
* @param featureBean
* @return personBean
*/
protected PersonBean daoSavePerson(PersonBean personBean, ImageBean idPhotoBean,
Collection featureBean) {
// delete old photo if exists
if(idPhotoBean != null){
daoDeleteImage(daoGetReferencedByImageMd5OnPerson(personBean));
}
return daoSavePerson(personBean, idPhotoBean, null, null, featureBean, null);
}
protected PersonBean daoSavePerson(PersonBean bean, ByteBuffer idPhoto, FeatureBean featureBean,
DeviceBean deviceBean) throws DuplicateRecordException {
ImageBean imageBean = daoAddImage(idPhoto, deviceBean, null, null);
return daoSavePerson(bean, imageBean, featureBean == null ? null : Arrays.asList(featureBean));
}
protected PersonBean daoSavePerson(PersonBean bean, ByteBuffer idPhoto, ByteBuffer feature,
String featureVersion, Map faceInfo, DeviceBean deviceBean) throws DuplicateRecordException {
return daoSavePerson(bean, idPhoto, daoAddFeature(feature, featureVersion, bean, faceInfo, deviceBean), deviceBean);
}
/**
*
* @param personBean 人员信息对象
* @param idPhoto 标准照图像
* @param feature 人脸特征数据
* @param featureVersion 特征(SDk)版本号
* @param featureImage 提取特征源图像,为null 时,默认使用idPhoto
* @param faceBean 人脸位置对象,为null 时,不保存人脸数据,忽略featureImage
* @param deviceBean featureImage来源设备对象
* @return personBean
* @throws DuplicateRecordException
*/
protected PersonBean daoSavePerson(PersonBean personBean, ByteBuffer idPhoto, ByteBuffer feature,
String featureVersion, ByteBuffer featureImage, FaceBean faceBean, DeviceBean deviceBean) throws DuplicateRecordException {
List faceList = faceBean == null ? null : Arrays.asList(faceBean);
if (Judge.isEmpty(featureImage)){
featureImage = idPhoto;
}
if(!Judge.isEmpty(idPhoto)){
/** 保存标准照 */
ImageBean imageBean = daoAddImage(idPhoto,deviceBean,featureImage == idPhoto ? faceList : null,null);
personBean.setImageMd5(imageBean.getMd5());
}
FeatureBean featureBean = null;
if (null != faceBean) {
if (!Judge.isEmpty(featureImage) && featureImage != idPhoto) {
/** 保存特征的同时保存featureImage */
featureBean = daoAddFeature(feature, featureVersion, personBean, ImmutableMap.of(featureImage, faceBean), deviceBean);
}else{
featureBean = daoAddFeature(feature, featureVersion, personBean, faceList);
}
}
return daoSavePerson(personBean, null, featureBean, deviceBean);
}
/**
* 删除personId指定的人员(person)记录及关联的所有记录
* @param personId
* @return 返回删除的记录数量
*/
@Override
protected int daoDeletePerson(Integer personId) {
PersonBean personBean = daoGetPerson(personId);
if(null == personBean){
return 0;
}
// 删除标准照
daoDeleteImage(personBean.getImageMd5());
daoDeleteAllFeaturesByPersonId(personId,true);
return super.daoDeletePerson(personId);
}
protected int daoDeletePersonByPapersNum(String papersNum) {
PersonBean personBean = daoGetPersonByIndexPapersNum(papersNum);
return null == personBean ? 0 : daoDeletePerson(personBean.getId());
}
protected int daoDeletePersonByPapersNum(Collection collection) {
int count =0;
if(null != collection){
for(String papersNum:collection){
count += daoDeletePersonByPapersNum(papersNum);
}
}
return count;
}
protected boolean daoIsDisable(int personId){
PersonBean personBean = daoGetPerson(personId);
if(null == personBean){
return true;
}
Date expiryDate = personBean.getExpiryDate();
return null== expiryDate?false:expiryDate.before(new Date());
}
protected void daoSetPersonExpiryDate(PersonBean personBean,Date expiryDate){
if(null != personBean){
personBean.setExpiryDate(expiryDate);
daoSavePerson(personBean);
}
}
protected void daoSetPersonExpiryDate(Collection personIdList,Date expiryDate){
if(null != personIdList){
for(Integer personId : personIdList){
daoSetPersonExpiryDate(daoGetPerson(personId),expiryDate);
}
}
}
protected List daoLoadUpdatedPersons(Date timestamp) {
LinkedHashSet updatedPersons = Sets.newLinkedHashSet(daoLoadPersonIdByUpdateTime(timestamp));
List idList = Lists.transform(
daoLoadFeatureByUpdateTime(timestamp),
daoCastFeatureToPersonId);
// 两个collection 合并去除重复
@SuppressWarnings("unused")
boolean b = Iterators.addAll(updatedPersons, Iterators.filter(idList.iterator(), Predicates.notNull()));
return Lists.newArrayList(updatedPersons);
}
/**
* 创建管理边界
* 设置fl_person_group.root_group和fl_device_group.root_group字段互相指向,
* 以事务操作方式更新数据库
* @param personGroupId 人员组id
* @param deviceGroupId 设备组id
* @throws ObjectRetrievalException 没有找到personGroupId或deviceGroupId指定的记录
*/
protected void daoBindBorder(final Integer personGroupId,final Integer deviceGroupId) throws ObjectRetrievalException{
final PersonGroupBean personGroup = daoGetPersonGroupChecked(personGroupId);
final DeviceGroupBean deviceGroup = daoGetDeviceGroupChecked(deviceGroupId);
{
// 解除与人员组已有的其他设备组绑定
Integer rootId = personGroup.getRootGroup();
if(rootId != null && rootId != deviceGroupId){
DeviceGroupBean root = daoGetDeviceGroup(rootId);
root.setRootGroup(null);
daoSaveDeviceGroup(root);
}
}
{
// 解除与设备组已有的其他人员组绑定
Integer rootId = deviceGroup.getRootGroup();
if(rootId != null && rootId != deviceGroupId){
PersonGroupBean root = daoGetPersonGroup(rootId);
root.setRootGroup(null);
daoSavePersonGroup(root);
}
}
personGroup.setRootGroup(deviceGroup.getId());
deviceGroup.setRootGroup(personGroup.getId());
daoSavePersonGroup(personGroup);
daoSaveDeviceGroup(deviceGroup);
}
/**
* 删除管理边界
* 删除fl_person_group.root_group和fl_device_group.root_group字段的互相指向,设置为{@code null},
* 以事务操作方式更新数据库
* 如果personGroupId和deviceGroupId不存在绑定关系则跳过
* @param personGroupId 人员组id
* @param deviceGroupId 设备组id
* @throws ObjectRetrievalException 没有找到personGroupId或deviceGroupId指定的记录
*/
protected void daoUnbindBorder(Integer personGroupId,Integer deviceGroupId) throws ObjectRetrievalException{
final PersonGroupBean personGroup = daoGetPersonGroupChecked(personGroupId);
final DeviceGroupBean deviceGroup = daoGetDeviceGroupChecked(deviceGroupId);
if(deviceGroup.getId().equals(personGroup.getRootGroup())
|| personGroup.getId().equals(deviceGroup.getRootGroup())){
personGroup.setRootGroup(null);
deviceGroup.setRootGroup(null);
daoRunAsTransaction(new Runnable() {
@Override
public void run() {
daoSavePersonGroup(personGroup);
daoSaveDeviceGroup(deviceGroup);
}
});
}
}
/**
* 返回personId所属的管理边界人员组id
* 在personId所属组的所有父节点中自顶向下查找第一个{@code fl_person_group.root_group}字段不为空的人员组,返回此记录组id
* @param personId
* @return {@code fl_person_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
* @throws ObjectRetrievalException 没有找到personId指定的记录
*/
protected Integer daoRootGroupOfPerson(Integer personId) throws ObjectRetrievalException{
Integer groupId = daoGetPersonChecked(personId).getGroupId();
return daoRootGroupOfPersonGroup(groupId);
}
/**
* 返回groupId指定人员组的管理边界人员组id
* 在groupId指定人员组的所有父节点中自顶向下查找第一个{@code fl_person_group.root_group}字段不为空的人员组,返回此记录组id
* @param groupId 人员组id
* @return {@code fl_person_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
*/
protected Integer daoRootGroupOfPersonGroup(Integer groupId) {
if(groupId == null){
return null;
}
// 循环引用检查
instanceOf(IPersonGroupManager.class).checkCycleOfParent(groupId);
List parents = daoListOfParentForPersonGroup(groupId);
Optional top = Iterables.tryFind(parents, new Predicate() {
@Override
public boolean apply(PersonGroupBean input) {
return input.getRootGroup()!=null;
}
});
return top.isPresent() ? top.get().getId() : null;
}
/**
* 返回deviceId所属的管理边界设备组id
* 在deviceId所属组的所有父节点中自顶向下查找第一个{@code fl_device_group.root_group}字段不为空的组,返回此记录id
* @param deviceId
* @return {@code fl_device_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
* @throws ObjectRetrievalException 没有找到deviceId指定的记录
*/
protected Integer daoRootGroupOfDevice(Integer deviceId) throws ObjectRetrievalException{
Integer groupId = daoGetDeviceChecked(deviceId).getGroupId();
return daoRootGroupOfDeviceGroup(groupId);
}
/**
* 返回groupId指定设备组的管理边界设备组id
* 在groupId指定设备组的所有父节点中自顶向下查找第一个{@code fl_device_group.root_group}字段不为空的组,返回此记录id
* @param groupId 设备组id
* @return {@code fl_device_group.root_group}字段不为空的记录id,没有找到则返回{@code null}
*/
protected Integer daoRootGroupOfDeviceGroup(Integer groupId){
if(groupId == null){
return null;
}
// 循环引用检查
instanceOf(IDeviceGroupManager.class).checkCycleOfParent(groupId);
List parents = daoListOfParentForDeviceGroup(groupId);
Optional top = Iterables.tryFind(parents, new Predicate() {
@Override
public boolean apply(DeviceGroupBean input) {
return input.getRootGroup()!=null;
}
});
return top.isPresent() ? top.get().getId() : null;
}
private LogBean checkLogBean(LogBean logBean){
if(logBean.getPersonId()==null){
String featureId = checkNotNull(Strings.emptyToNull(logBean.getVerifyFeature()),
"NOT FOUND valid person id caused by fl_log.verify_feature is null");
FeatureBean featureBean = checkNotNull(daoGetFeature(featureId),
"NOT FOUND valid person id caused by invalid feature id %s",featureId);
logBean.setPersonId(checkNotNull(featureBean.getPersonId(),
"NOT FOUND valid person id caused by fl_feature.person_id is null"));
} /*else {
// 检查verifyFeature记录所属的person_id是否与log的person_id相等
String featureId = logBean.getVerifyFeature();
if(!Strings.isNullOrEmpty(featureId)){
FeatureBean featureBean = checkNotNull(dm.daoGetFeature(featureId),
"INVALID feature id %s",featureId);
checkArgument(logBean.getPersonId().equals(featureBean.getPersonId()),
"MISMATCH person id for VerifyFeature");
}
}*/
return logBean;
}
@Override
protected LogBean daoAddLog(LogBean logBean) throws RuntimeDaoException, DuplicateRecordException {
return super.daoAddLog(checkLogBean(logBean));
}
@Override
protected LogBean daoAddLog(LogBean logBean, DeviceBean refDeviceByDeviceId, FaceBean refFaceByCompareFace,
FeatureBean refFeatureByVerifyFeature, ImageBean refImageByImageMd5, PersonBean refPersonByPersonId)
throws RuntimeDaoException, DuplicateRecordException {
return super.daoAddLog(checkLogBean(logBean), refDeviceByDeviceId, refFaceByCompareFace, refFeatureByVerifyFeature,refImageByImageMd5,
refPersonByPersonId);
}
/**
* 添加一条验证日志记录
*
{@code DEVICE_ONLY}
* @param logBean 日志记录对象
* @param faceBean 需要保存到数据库的提取人脸特征的人脸信息对象
* @param featureImage 需要保存到数据库的现场采集人脸特征的照片
* @throws DuplicateRecordException 数据库中存在相同记录
*/
protected LogBean daoAddLog(LogBean logBean,FaceBean faceBean,ByteBuffer featureImage) throws DuplicateRecordException {
if(logBean == null || faceBean == null || featureImage == null){
return null;
}
PersonBean personBean = daoGetReferencedByPersonIdOnLog(logBean);
DeviceBean deviceBean = daoGetReferencedByDeviceIdOnLog(logBean);
daoAddImage(featureImage, deviceBean,
faceBean == null ? null : Arrays.asList(faceBean), null);
return daoAddLog(logBean, deviceBean, faceBean, null, null,personBean);
}
enum ErrorCatalog{
SERVICE,DEVICE,APPLICATION
}
private ErrorLogBean checkErrorLogBean(ErrorLogBean logBean){
String catalog = checkNotNull(logBean.getCatalog(),"catalog field is null");
Matcher machter = Pattern.compile("(\\w+)(/\\w+)+").matcher(catalog);
checkArgument(machter.matches(),"INVALID catalog format [%s],such as [device/network] required",catalog);
String level1 = machter.group(1);
try {
ErrorCatalog.valueOf(level1.toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("INVALID catalog level 1:" + level1);
}
return logBean;
}
@Override
protected ErrorLogBean daoAddErrorLog(ErrorLogBean errorLogBean)
throws RuntimeDaoException, DuplicateRecordException {
return super.daoAddErrorLog(checkErrorLogBean(errorLogBean));
}
private volatile PersonBean dummyUser;
private PersonBean getDummyUser(){
// double check
if(dummyUser == null){
synchronized (this) {
if(dummyUser == null){
PersonBean tmpl = PersonBean.builder().name(DUMMY_USER).build();
List users = daoLoadPersonUsingTemplate(tmpl, 1, -1);
if(users.isEmpty()){
dummyUser = daoSavePerson(tmpl);
} else{
dummyUser = users.get(0);
}
}
}
}
return dummyUser;
}
protected LogBean daoAddLog(LogBean logBean,ByteBuffer faceImage,Integer deviceId) throws DuplicateRecordException {
if(logBean == null){
logBean = new LogBean();
}
if(faceImage == null){
return null;
}
PersonBean person;
if(logBean.getPersonId() == null){
person = getDummyUser();
logBean.setPersonId(person.getId());
}else{
person = daoGetPersonChecked(logBean.getPersonId());
}
DeviceBean deviceBean = daoGetDevice(deviceId);
ImageBean imageBean = daoAddImage(faceImage, deviceBean, null, null);
return daoAddLog(logBean, deviceBean, null, null, imageBean,person);
}
/**
* 设置 personId 指定的人员为禁止状态
* @param personId
* @param moveToGroupId 将用户移动到指定的用户组,为{@code null}则不移动
* @param deletePhoto 为{@code true}删除用户标准照
* @param deleteFeature 为{@code true}删除用户所有的人脸特征数据(包括照片)
* @param deleteLog 为{@code true}删除用户所有通行日志
*/
protected void daoDisablePerson(int personId, Integer moveToGroupId,
boolean deletePhoto, boolean deleteFeature, boolean deleteLog){
PersonBean personBean = daoGetPerson(personId);
if(personBean == null){
return;
}
// 有效期设置为昨天
Date yesterday = new Date(System.currentTimeMillis() - 86400000L);
personBean.setExpiryDate(yesterday);
if(moveToGroupId != null){
personBean.setGroupId(moveToGroupId);
}
daoSavePerson(personBean);
if(deletePhoto){
// 删除标准照
daoDeleteImage(personBean.getImageMd5());
}
if(deleteFeature){
daoDeleteAllFeaturesByPersonId(personId,true);
}
if(deleteLog){
daoDeleteLogBeansByPersonIdOnPerson(personId);
}
}
/** 允许修改的fl_permit表字段ID */
private static final HashSet allowedPermitColumns=Sets.newHashSet(FL_PERMIT_ID_SCHEDULE,
FL_PERMIT_ID_PASS_LIMIT);
/**
* 修改指定记录的的字段值(String类型)
* 如果记录不存在则创建deviceGroupId和personGroupId之间的MANY TO MANY 联接表(fl_permit)记录,
* @param deviceGroupId 设备组id
* @param personGroupId 人员组id
* @param column 字段名,允许的字段名为'schedule','pass_limit'
* @param value 字段值
* @return (fl_permit)记录
*/
protected PermitBean daoSavePermit(int deviceGroupId,int personGroupId,String column, String value){
int columnID = -1;
if(column != null){
columnID=RowMetaData.getMetaData(PermitBean.class).columnIDOf(column);
checkArgument(columnID>=0,"invalid column name %s",column);
checkArgument(allowedPermitColumns.contains(columnID),"column %s can't be modify",column);
}
PermitBean permitBean = daoGetPermit(deviceGroupId, personGroupId);
if(permitBean == null){
permitBean = PermitBean.builder()
.deviceGroupId(deviceGroupId)
.personGroupId(personGroupId)
.build();
}
if(columnID >= 0){
permitBean.setValue(columnID, value);
}
return daoSavePermit(permitBean);
}
/**
* 获取指定人脸特征关联的人脸特征记录
* @param imageMd5 图像数据的MD校验码,为空或{@code null}或记录不存在返回空表
* @return 特征记录id列表
*/
protected List daoGetFeaturesOfImage(String imageMd5){
if(Strings.isNullOrEmpty(imageMd5)){
return Collections.emptyList();
}
List faces = daoGetFaceBeansByImageMd5OnImage(imageMd5);
Iterable features = Iterables.filter(Lists.transform(faces, daoCastFaceToFeatureMd5),Predicates.notNull());
return Lists.newArrayList(features);
}
/**
* 获取指定人脸特征关联的人脸记录
* @param featureMd5 人脸特征记录id(MD校验码),为空或{@code null}或记录不存在返回空表
* @return {@link FaceBean}列表
*/
protected List daoGetFacesOfFeature(String featureMd5){
if(Strings.isNullOrEmpty(featureMd5)){
return Collections.emptyList();
}
return daoGetFaceBeansByFeatureMd5OnFeature(featureMd5);
}
/**
* 获取指定图像关联的人脸记录
* @param imageMd5 图像数据的MD校验码,为空或{@code null}或记录不存在返回空表
* @return {@link FaceBean}列表
*/
protected List daoGetFacesOfImage(String imageMd5){
if(Strings.isNullOrEmpty(imageMd5)){
return Collections.emptyList();
}
return daoGetFaceBeansByImageMd5OnImage(imageMd5);
}
private static final Function dateCaster = new Function(){
@Override
public LogBean apply(LogBean bean) {
if(null == bean){
return null;
}
Date v = bean.getVerifyTime();
if(v != null){
v = bean.getCreateTime();
}
if(v != null){
Calendar calendar=Calendar.getInstance();
calendar.setTime(v);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
bean.setVerifyTime(calendar.getTime());
}
return bean;
}};
/**
* 按天统计指定用户的通行次数
* @param personId
* @param startDate 统计起始日期,可为{@code null}
* @param endDate 统计结束日期,可为{@code null}
* @return 返回统计结果,即每个有通行记录的日期(格式:yyyy-MM-dd)的通行次数
*/
protected Map daoCountPersonLog(int personId,Date startDate,Date endDate){
SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMATTER_STR);
StringBuffer buffer = new StringBuffer("WHERE");
buffer.append(" person_id=").append(personId);
checkArgument(null == startDate || null == endDate || startDate.getTime() <=endDate.getTime(),"start must <= end");
if(startDate != null){
buffer.append(" and");
buffer.append(String.format(" date(verify_time)>='%s'",formatter.format(startDate)));
}
if(endDate != null){
buffer.append(" and");
buffer.append(String.format(" date(verify_time)>='%s'",formatter.format(endDate)));
}
List logList = daoLoadLogByWhere(buffer.toString(), 1, -1);
Lists.transform(logList, dateCaster);
Mapstat=Maps.newHashMap();
for(LogBean log:logList){
Date d = log.getVerifyTime();
if(d != null){
String key = formatter.format(d);
Integer c = MoreObjects.firstNonNull(stat.get(key), 0);
stat.put(key, c + 1);
}
}
return stat;
}
/**
* 根据图像的MD5校验码返回图像数据
* @param imageMD5
* @return 二进制数据字节数组,如果数据库中没有对应的数据则返回null
*/
protected byte[] daoGetImageBytes(String imageMD5) throws IOException{
StoreBean storeBean = daoGetStore(imageMD5);
return null ==storeBean?null:BinaryUtils.getBytes(storeBean.getData());
}
/**
* 根据提供的主键ID,返回图像数据
* @param primaryKey 主键
* @param refType 主键引用类型
* @return 二进制数据字节数组,如果数据库中没有对应的数据则返回null
* @throws IOException
*/
protected byte[] daoGetImageBytes(String primaryKey,RefSrcType refType) throws IOException{
ImageBean bean = daoGetImage(primaryKey,refType);
return null == bean ? null : daoGetImageBytes(bean.getMd5());
}
/**
* 根据提供的主键ID,返回图像数据记录
* @param primaryKey 主键
* @param refType 主键引用类型
* @return 图像数据记录
*/
protected ImageBean daoGetImage(String primaryKey,RefSrcType refType) {
checkArgument(refType!=null,"refType is null");
if(null == primaryKey){
return null;
}
switch (MoreObjects.firstNonNull(refType,RefSrcType.DEFAULT)) {
case PERSON:
{
PersonBean bean = daoGetPerson(Integer.valueOf(primaryKey));
return null == bean ? null : daoGetImage(bean.getImageMd5());
}
case FACE:
{
FaceBean bean = daoGetFace(Integer.valueOf(primaryKey));
return null == bean ? null : daoGetImage(bean.getImageMd5());
}
case FEATURE:
{
List beans = daoGetImageKeysImportedByFeatureMd5(primaryKey);
return beans.size() > 0 ? daoGetImage(beans.get(0)) : null;
}
case LOG:
{
LogBean logBean = daoGetLog(Integer.valueOf(primaryKey));
// 获取现场采集图像数据的优先序为: compare_face,image_md5
ImageBean imageBean = null == logBean ? null : daoGetImage(logBean.getCompareFace() == null ? null : logBean.getCompareFace().toString(),RefSrcType.FACE);
if(imageBean == null){
return daoGetImage(logBean.getImageMd5());
}
return imageBean;
}
case LIGHT_LOG:
{
return daoGetImage(primaryKey,RefSrcType.LOG);
}
case IMAGE:
case DEFAULT:
return daoGetImage(primaryKey);
default:
return null;
}
}
/**
* MAC地址归一化处理
* @param mac
* @return
*/
private static final String normalizeMac(String mac){
// mac地址保存为小写强制转小写查找
return mac == null ? null : mac.toLowerCase();
}
private static final Function normalizeDeviceBean = new Function(){
@Override
public DeviceBean apply(DeviceBean input) {
if(input == null || input.getMac() == null){
return input;
}
int modified = input.getModified();
input.setMac(normalizeMac(input.getMac()));
input.setModified(modified);
return input;
}};
@Override
protected DeviceBean daoGetDeviceByIndexMac(String mac)
throws RuntimeDaoException{
return super.daoGetDeviceByIndexMac(normalizeMac(mac));
}
@Override
protected DeviceBean daoSaveDevice(DeviceBean deviceBean)
throws RuntimeDaoException{
return super.daoSaveDevice(normalizeDeviceBean.apply(deviceBean));
}
/**
* 个人信息脱敏转换器
*/
protected static final Function DESENSITIZATION_TRANSFORMER= new Function(){
@Override
public PersonBean apply(PersonBean input) {
if(input != null){
PersonBean output = input.clone();
if(null != output.getName()){
output.setName(output.getName().replaceAll("\\S", "*"));
}
if(null != output.getPapersNum()){
output.setPapersNum(output.getPapersNum().replaceAll("\\S", "*"));
}
if(null != output.getMobilePhone()){
output.setMobilePhone(output.getMobilePhone().replaceAll("\\S", "*"));
}
return output;
}
return input;
}};
/**
* 个人信息脱敏
* @param input 输入数据
* @return 输出脱敏数据
*/
protected static final PersonBean desensitize(PersonBean input) {
return DESENSITIZATION_TRANSFORMER.apply(input);
}
/**
* 个人信息脱敏
* @param input 输入数据
* @return 输出脱敏数据
*/
protected static final List desensitize(List input) {
return input == null ? null : Lists.transform(input, DESENSITIZATION_TRANSFORMER);
}
/**
* 返回'name'字段为指定值的所有记录
* @param interfaceClass
* @param name
* @param filter 用于筛选记录的过滤器,可为{@code null}
* @return 返回结果结录列表,没找到则返回空表
*/
protected >
List
daoGetBeansByName(ClassinterfaceClass,String name, Predicate filter){
List beans = instanceOf(interfaceClass).loadByWhereAsList(String.format("WHERE name='%s'",name));
if(filter != null){
return Lists.newArrayList(Iterables.filter(beans, filter));
}
return beans;
}
protected >
String
daoGroupPathOf(ClassinterfaceClass,Integer id){
M manager = instanceOf(interfaceClass);
try {
@SuppressWarnings({ "unchecked" })
List parentList = (List) interfaceClass.getMethod("listOfParent", Integer.class).invoke(manager, id);
List parentNames = Lists.transform(parentList, new Function(){
@Override
public String apply(B input) {
return input.getValue("name");
}});
return Joiner.on('/').join(parentNames);
} catch (InvocationTargetException e) {
Throwables.throwIfUnchecked(e.getTargetException());
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
protected >
String
daoGroupPathOf(ClassinterfaceClass,B group){
return daoGroupPathOf(interfaceClass,(Integer)checkNotNull(group,"group is null").getValue("id"));
}
private >
void
deleteElements(ClassinterfaceClass, B child){
M manager = instanceOf(interfaceClass);
try {
String elementTypeName = child.getClass().getSimpleName().replace("Group", "");
String methodName = String.format("delete%ssByGroupId", elementTypeName);
interfaceClass.getMethod(methodName, Integer.class).invoke(manager, child.getValue("id"));
} catch (InvocationTargetException e) {
Throwables.throwIfUnchecked(e.getTargetException());
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
String
daoPathOf(String tablename,Integer id){
if("fl_device_group".equals(tablename)){
return daoGroupPathOf(IDeviceGroupManager.class, id);
}else if("fl_person_group".equals(tablename)){
return daoGroupPathOf(IPersonGroupManager.class, id);
}else{
throw new IllegalArgumentException(String.format("INVALID tablename [%s],'fl_device_group','fl_person_group' required",tablename));
}
}
/**
* 删除指定组下的所有子节点
* @param interfaceClass
* @param groupId
* @param deleteElement 是否删除组中的元素(人员/设备记录)
*/
protected >
void
daoDeleteChilds(ClassinterfaceClass, Integer groupId, final boolean deleteElement){
M manager = instanceOf(interfaceClass);
try {
@SuppressWarnings("unchecked")
Listchilds = (List) interfaceClass.getMethod("childListByParent", Integer.class).invoke(manager, groupId);
for(B child:childs){
if(deleteElement){
deleteElements(interfaceClass,child);
}
manager.delete(child);
}
} catch (InvocationTargetException e) {
Throwables.throwIfUnchecked(e.getTargetException());
throw new RuntimeException(e.getTargetException());
} catch (Exception e) {
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
/**
* 删除指定组下的所有子节点
* @param interfaceClass
* @param groupId
* @param deleteElement 是否删除组中的元素(人员/设备记录)
*/
protected >
void
daoDeleteChilds(ClassinterfaceClass, B groupId, final boolean deleteElement){
daoDeleteChilds(
interfaceClass,
(Integer)checkNotNull(groupId,"group is null").getValue("id"),
deleteElement);
}
/**
* 删除指定组并删除指定组下的所有子节点
* @param interfaceClass
* @param groupId 组ID
* @param deleteElement deleteElement 是否删除组中的元素(人员/设备记录)
*/
protected >
void
daoDeleteTree(ClassinterfaceClass, Integer groupId, boolean deleteElement){
M manager = instanceOf(interfaceClass);
B bean = manager.loadByPrimaryKey(groupId);
if(bean != null){
daoDeleteChilds(interfaceClass,groupId, deleteElement);
if(deleteElement){
deleteElements(interfaceClass,bean);
}
manager.delete(bean);
}
}
/**
* 删除指定组并删除指定组下的所有子节点
* @param interfaceClass
* @param group
* @param deleteElement deleteElement 是否删除组中的元素(人员/设备记录)
*/
protected >
void
daoDeleteTree(ClassinterfaceClass, B group, boolean deleteElement){
daoDeleteTree(
interfaceClass,
(Integer)checkNotNull(group,"group is null").getValue("id"),
deleteElement);
}
/**
* 获取{@code parent}指定的组下不重复的唯一名字
* @param interfaceClass
* @param name
* @param parent
* @param cleanMore
* @param deleteElement 是否删除组中的元素(人员/设备记录)
* @param found
* @return
*/
private >
String
getUniqueName(ClassinterfaceClass,final String name, final Integer parent,boolean cleanMore, boolean deleteElement, AtomicReference found){
int c = 0;
String n = name;
do{
// 返回parent指定的组下所有指定名称的记录
List groups = daoGetBeansByName(interfaceClass,n, new Predicate() {
@Override
public boolean apply(B input) {
return Objects.equal(parent, input.getValue("parent"));
}
});
if(groups.size() > 1){
if(cleanMore){
// 删除多余的同名记录
for(int i = 1; i < groups.size();++i){
B d = groups.get(i);
daoDeleteTree(interfaceClass, d, deleteElement);
logger.info("Delete duplicated group tree {}[{}] on {}",
daoGroupPathOf(interfaceClass, d),
d.getValue("id"),
d.tableName());
}
if(found != null){
found.set(groups.get(0));
}
return n;
}else{
n = String.format("%s(%d)", name,++c);
continue;
}
}else if(groups.size() == 1){
if(found != null){
found.set(groups.get(0));
}
return n;
}else{
found.set(null);
return n;
}
}while(true);
}
/**
* 创建组节点
* @param interfaceClass
* @param name
* @param parent
* @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个
* @param deleteElement 删除组时是否删除组下的元素(设备/人员)
* @return
*/
protected >
B
daoCreateGroupByNameIfNoExist(ClassinterfaceClass,String name,Integer parent, boolean cleanMore, boolean deleteElement){
M manager = instanceOf(interfaceClass);
AtomicReference found = new AtomicReference<>();
name = getUniqueName(interfaceClass,name,parent,cleanMore, deleteElement, found);
if(found.get() == null){
B group = manager.createBean();
group.setValue("name", name);
group.setValue("parent", parent);
manager.save(group);
logger.info("Create group {}[{}] on {}",
daoGroupPathOf(interfaceClass, group),
group.getValue("id"),
group.tableName());
return group;
}
logger.info("Exist group {}[{}] on {}",
daoGroupPathOf(interfaceClass,found.get()),
found.get().getValue("id"),found.get().tableName());
return found.get();
}
/**
* 根据{@code cleanExistingTop}参数标志删除存在同名顶级组节点
* @param interfaceClass
* @param name 顶级组名
* @param cleanExistingTop 是否删除存在的同名顶级组节点
* @param deleteElement 是否删除组中的元素(人员/设备记录)
*/
private >
void
deleteTopGroupIfExist(ClassinterfaceClass, String name, boolean cleanExistingTop, boolean deleteElement){
if(cleanExistingTop){
// 删除存在的所有同名顶级组
List groups = daoGetBeansByName(interfaceClass,name,new Predicate() {
@Override
public boolean apply(B input) {
return input.getValue("parent") == null;
}
});
for(B group:groups){
daoDeleteTree(interfaceClass,group, deleteElement);
logger.info("REMOVE top group {}[{}] on {}",group.getValue("name"),group.getValue("id"),group.tableName());
}
}
}
/**
* 创建顶级组节点
* @param interfaceClass
* @param name
* @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个
* @param deleteElement 删除组时是否删除组下的元素(设备/人员)
* @return
*/
protected >
B
daoCreateTopGroupIfNoExist(ClassinterfaceClass, String name, boolean cleanMore, boolean deleteElement){
B group = daoCreateGroupByNameIfNoExist(interfaceClass,name,null, cleanMore, false);
checkArgument(group.getValue("parent") == null, "group %s:%s is not top group",group.tableName(),name);
return group;
}
/**
* 根据路径创建组节点
* @param interfaceClass
* @param path
* @param cleanMore 当存在同名的多个节点时是否清除多余的节点只保留1个
* @param deleteElement 删除组时是否删除组下的元素(设备/人员)
* @return
*/
protected >
B[]
daoCreateGroupByPathIfNoExist(ClassinterfaceClass,String path, boolean cleanMore, boolean deleteElement){
String[] nodes = TopGroupInfo.parseNodes(path);
@SuppressWarnings("unchecked")
B[] nodeBeans = (B[]) Array.newInstance(instanceOf(interfaceClass).createBean().getClass(), nodes.length);
if(nodes.length > 0){
logger.info("Create path {}",Joiner.on('/').join(nodes));
B bean = daoCreateTopGroupIfNoExist(interfaceClass, nodes[0], cleanMore, deleteElement);
Integer parent = bean.getValue("id");
nodeBeans[0] = bean;
for(int i = 1; i < nodes.length; ++i){
bean = daoCreateGroupByNameIfNoExist(interfaceClass, nodes[i], parent, cleanMore, deleteElement);
nodeBeans[i] = bean;
parent = bean.getValue("id");
}
}
return nodeBeans;
}
protected >
List
daoGetGroupsByPath(final ClassinterfaceClass,final String path){
String[] nodes = TopGroupInfo.parseNodes(path);
if(nodes.length > 0){
return daoGetBeansByName(interfaceClass, nodes[nodes.length-1], new Predicate() {
@Override
public boolean apply(B input) {
return path.equals(daoGroupPathOf(interfaceClass,input));
}
});
}
return Collections.emptyList();
}
protected
List
daoGetGroupIdsByPath(String tablename,String path){
if("fl_device_group".equals(tablename)){
return Lists.transform(daoGetGroupsByPath(IDeviceGroupManager.class, path), daoCastDeviceGroupToPk);
}else if("fl_person_group".equals(tablename)){
return Lists.transform(daoGetGroupsByPath(IPersonGroupManager.class, path), daoCastPersonGroupToPk);
}else{
throw new IllegalArgumentException(String.format("INVALID tablename [%s],'fl_device_group','fl_person_group' required",tablename));
}
}
protected >
B
daoGetGroupByPath(final ClassinterfaceClass,final String path){
String[] nodes = TopGroupInfo.parseNodes(path);
if(nodes.length > 0){
List nodeBeans = daoGetBeansByName(interfaceClass, nodes[nodes.length-1], new Predicate() {
@Override
public boolean apply(B input) {
return path.equals(daoGroupPathOf(interfaceClass,input));
}
});
switch(nodeBeans.size()){
case 0:
return null;
case 1:
return nodeBeans.get(0);
default:
throw new IllegalArgumentException(String.format("FOUND multi record in %s,path=%s",
nodeBeans.get(0).tableName(),path));
}
}
return null;
}
/**
* 创建叶子节点
* @param interfaceClass
* @param parent
* @param name
* @param cleanMore
* @return
*/
protected >
B
daoCreateLeafIfNoExist(ClassinterfaceClass,int parent,String name, boolean cleanMore){
checkArgument(!Strings.isNullOrEmpty(name),"name is null or empty");
B bean;
M manager = instanceOf(interfaceClass);
B[] found = manager.loadByWhere(String.format("WHERE name='%s' AND parent=%d",name,parent));
if(found.length > 0){
if(found.length > 1 && cleanMore ){
for(int i = 1; i < found.length; ++i){
manager.delete(found[i]);
}
}
bean = found[0];
logger.info("Exist leaf group {}[{}] on {}",
daoGroupPathOf(interfaceClass, bean),
bean.getValue("id"),
bean.tableName());
}else{
bean = manager.createBean();
bean.setValue("name", name);
bean.setValue("parent", parent);
bean.setValue("leaf", 1);
manager.save(bean);
logger.info("Create leaf group {}[{}] on {}",
daoGroupPathOf(interfaceClass, bean),
bean.getValue("id"),
bean.tableName());
}
return bean;
}
/**
* 创建叶子节点
* @param interfaceClass
* @param parent
* @param names
* @param cleanMore
* @return
*/
protected >
B[]
daoCreateLeafBeansIfNoExist(ClassinterfaceClass,int parent,String names, boolean cleanMore){
List list = MiscellaneousUtils.elementsOf(names);
@SuppressWarnings("unchecked")
B[] ids = (B[]) Array.newInstance(instanceOf(interfaceClass).createBean().getClass(),list.size());
for(int i = 0 ; i < list.size(); ++i){
ids[i] = daoCreateLeafIfNoExist(interfaceClass,parent,list.get(i),cleanMore);
}
return ids;
}
private void permitPath(PersonGroupBean permitPersonGroup,String devicePath){
DeviceGroupBean deviceGroup = daoGetGroupByPath(IDeviceGroupManager.class, devicePath);
if(deviceGroup != null){
daoSavePermit(deviceGroup.getId(), permitPersonGroup.getId(), null, null);
logger.info("Permit person group {}[{}] on device {}[{}]",
daoGroupPathOf(IPersonGroupManager.class, permitPersonGroup),
permitPersonGroup.getId(),
devicePath,
deviceGroup.getId());
}else{
logger.warn("NO FOUND device path {}",devicePath);
}
}
/**
* 根据输入的{@link TopGroupInfo}对象创建顶级组(人员/设备)其下子节点
* @param groupInfo
* @return 顶级人员组id
*/
protected int initTopGroup(TopGroupInfo groupInfo){
checkArgument(groupInfo != null,"groupInfo is null");
checkArgument(!Strings.isNullOrEmpty(groupInfo.getName()),"name is null or empty");
groupInfo.normalize();
deleteTopGroupIfExist(IDeviceGroupManager.class,groupInfo.getName(),groupInfo.isCleanExistingTop(), groupInfo.isDeleteElement());
deleteTopGroupIfExist(IPersonGroupManager.class,groupInfo.getName(),groupInfo.isCleanExistingTop(), groupInfo.isDeleteElement());
DeviceGroupBean topDeviceGroup = daoCreateTopGroupIfNoExist(IDeviceGroupManager.class, groupInfo.getName(), groupInfo.isClearMore(), groupInfo.isDeleteElement());
PersonGroupBean topPersonGroup = daoCreateTopGroupIfNoExist(IPersonGroupManager.class, groupInfo.getName(), groupInfo.isClearMore(), groupInfo.isDeleteElement());
daoBindBorder(topPersonGroup.getId(), topDeviceGroup.getId());
logger.info("==BindBorder person top group {}[{}] VS device top person {}[{}]",
topPersonGroup.getName(),
topPersonGroup.getId(),
topDeviceGroup.getName(),
topDeviceGroup.getId());
if(!groupInfo.getNodes().isEmpty()){
logger.info("==============================");
logger.info("====Create deive/permit groups pairs=====");
logger.info("==============================");
// 创建配对的设备组人员组并授权
for(Entry entry:groupInfo.getNodes().entrySet()){
String path = groupInfo.fullPathOf( entry.getKey());
String leafs = entry.getValue();
DeviceGroupBean[] deviceGroupParentBeans = daoCreateGroupByPathIfNoExist(IDeviceGroupManager.class, path, groupInfo.isClearMore(), false);
PersonGroupBean[] personGroupParentBeans = daoCreateGroupByPathIfNoExist(IPersonGroupManager.class, path, groupInfo.isClearMore(), false);
checkState(deviceGroupParentBeans.length == personGroupParentBeans.length,"MISMATCH length between deviceGroupIds and personGroupIds");
for(int i = 0; i < deviceGroupParentBeans.length; i++){
daoSavePermit(deviceGroupParentBeans[i].getId(), personGroupParentBeans[i].getId(), null, null);
logger.info("Permit person group {}[{}] ON device {}[{}]",
daoGroupPathOf(IPersonGroupManager.class, personGroupParentBeans[i]),
personGroupParentBeans[i].getId(),
daoGroupPathOf(IDeviceGroupManager.class,deviceGroupParentBeans[i]),
deviceGroupParentBeans[i].getId());
}
DeviceGroupBean[] deviceGroupLeafBeans = daoCreateLeafBeansIfNoExist(IDeviceGroupManager.class, deviceGroupParentBeans[deviceGroupParentBeans.length-1].getId(), leafs, groupInfo.isClearMore());
PersonGroupBean[] personGroupLeftBeans = daoCreateLeafBeansIfNoExist(IPersonGroupManager.class, personGroupParentBeans[personGroupParentBeans.length-1].getId(), leafs, groupInfo.isClearMore());
for(int i = 0; i < deviceGroupLeafBeans.length; ++i){
daoSavePermit(deviceGroupLeafBeans[i].getId(), personGroupLeftBeans[i].getId(), null, null);
logger.info("Permit person group {}[{}] on device {}[{}]",
daoGroupPathOf(IPersonGroupManager.class, personGroupLeftBeans[i]),
personGroupLeftBeans[i].getId(),
daoGroupPathOf(IDeviceGroupManager.class,deviceGroupLeafBeans[i]),
deviceGroupLeafBeans[i].getId());
}
}
}
if(!groupInfo.getPermits().isEmpty()){
logger.info("==============================");
logger.info("===========Create Permits==========");
logger.info("==============================");
// 对指定人员组授权指定的设备组
for(Entry entry:groupInfo.getPermits().entrySet()){
String personGroupPath = groupInfo.fullPathOf(Strings.nullToEmpty(entry.getKey()));
String deviceLeafs = Strings.nullToEmpty(entry.getValue());
if(!Strings.isNullOrEmpty(deviceLeafs)){
PersonGroupBean[] personGroupIds = daoCreateGroupByPathIfNoExist(IPersonGroupManager.class, personGroupPath, groupInfo.isClearMore(), false);
PersonGroupBean permitPersonGroup = personGroupIds[personGroupIds.length - 1];
String[] entries = deviceLeafs.split("[,]");
if(entries.length > 0){
String prefix = groupInfo.fullPathOf(entries[0]);
if(entries.length == 2){
if(entries[1].equals("*")){
// 第二个元素为通配符则列出所有子节点
DeviceGroupBean prefixDeviceGroup = daoGetGroupByPath(IDeviceGroupManager.class, prefix);
if(prefixDeviceGroup != null){
List childs = daoGetSubDeviceGroup(prefixDeviceGroup.getId());
List childNames = Lists.newArrayList(Iterables.transform(childs, new Function(){
@Override
public String apply(DeviceGroupBean input) {
return input.getName();
}}));
childNames.add(0, entries[0]);
entries = childNames.toArray(new String[0]);
}else{
logger.warn("NO FOUND device path {}",prefix);
continue;
}
}
}
for(int i = 1; i < entries.length; ++i){
if(!entries[i].isEmpty()){
permitPath(permitPersonGroup, prefix + "/" + entries[i]);
}
}
permitPath(permitPersonGroup,prefix);
}
}
}
}
return topPersonGroup.getId();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy