Commit 66f8add0 by Kang Donghun

#73114 作業同期 ContentRefresher修正

parent b613c708
......@@ -114,7 +114,11 @@ public class AcmsClient implements AcmsClientResponseListener {
private Date lastPresentTime = DateTimeUtil.getCurrentDate(); // 最後の通信時に取得したサーバの時間
private int mRetryCount;
/** masterData 専用の join タイムアウト倍率(並列 fetch 時もスレッドごとに独立) */
private static final ThreadLocal<Integer> masterDataJoinMultiplier = new ThreadLocal<Integer>();
/** 並列 API 時も lastCMSAccess 更新だけ直列化し、SQLite 競合を避ける */
private static final Object ACMS_MEMBER_LAST_ACCESS_LOCK = new Object();
/**
* {@link AcmsClient} のインスタンスを初期化します。
* @since 1.0.0
......@@ -822,13 +826,15 @@ public class AcmsClient implements AcmsClientResponseListener {
HttpResponse response = send(apiUrl, methodName, param, ignore404);
// 最終アクセス時間更新
MemberInfoDao dao = AbstractDao.getDao(MemberInfoDao.class);
MemberInfoDto dto = dao.getMemberInfo(LoginStatus.LoggedIn.statusCode());
if (dto != null) {
dto.lastCMSAccessDate = DateTimeUtil.getCurrentTimestamp();
dao.updateMemberInfo(dto);
// 最終アクセス時間更新(並列リクエスト時の DB 競合を避けるため直列化)
synchronized (ACMS_MEMBER_LAST_ACCESS_LOCK) {
MemberInfoDao dao = AbstractDao.getDao(MemberInfoDao.class);
MemberInfoDto dto = dao.getMemberInfo(LoginStatus.LoggedIn.statusCode());
if (dto != null) {
dto.lastCMSAccessDate = DateTimeUtil.getCurrentTimestamp();
dao.updateMemberInfo(dto);
}
}
return response;
......@@ -850,8 +856,10 @@ public class AcmsClient implements AcmsClientResponseListener {
try {
httpTaskThread.start();
if (methodName.equals(AcmsApis.ApiGetMasterData)) { //マスタデータ取得失敗時、タイムアウト設定する
long connectionTimeout = HttpRequestSender.DefaultConnectionTimeout * (mRetryCount + 1);
Logger.i(TAG, "ApiGetMasterData mRetryCount = " + mRetryCount + ", time out = " + connectionTimeout);
Integer mult = masterDataJoinMultiplier.get();
int m = mult != null ? mult.intValue() : 0;
long connectionTimeout = HttpRequestSender.DefaultConnectionTimeout * (m + 1);
Logger.i(TAG, "ApiGetMasterData joinMultiplier = " + m + ", time out = " + connectionTimeout);
httpTaskThread.join(connectionTimeout);
} else {
httpTaskThread.join(HttpRequestSender.DefaultConnectionTimeout);
......@@ -1058,10 +1066,13 @@ public class AcmsClient implements AcmsClientResponseListener {
* @since 1.0.0
*/
public MasterDataJSON masterData(AcmsParameters param, int retryCount) throws NetworkDisconnectedException, AcmsException {
this.mRetryCount = retryCount;
HttpResponse response = send(AcmsApis.ApiGetMasterData, param);
MasterDataJSON json = new MasterDataJSON(response.httpResponseBody);
return json;
masterDataJoinMultiplier.set(Integer.valueOf(retryCount));
try {
HttpResponse response = send(AcmsApis.ApiGetMasterData, param);
return new MasterDataJSON(response.httpResponseBody);
} finally {
masterDataJoinMultiplier.remove();
}
}
/**
......
......@@ -7,9 +7,16 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.acms.client.json.CategoriesJSON;
import jp.agentec.abook.abv.bl.acms.client.json.ContentVersionsJSON;
import jp.agentec.abook.abv.bl.acms.client.parameters.ContentDownloadLogParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.FetchDateParameters;
......@@ -23,9 +30,12 @@ import jp.agentec.abook.abv.bl.data.ABVDataCache;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.data.dao.AcmsDao;
import jp.agentec.abook.abv.bl.data.dao.ContentDao;
import jp.agentec.abook.abv.bl.data.dao.MemberInfoDao;
import jp.agentec.abook.abv.bl.data.dao.SppDeviceDao;
import jp.agentec.abook.abv.bl.dto.ContentDto;
import jp.agentec.abook.abv.bl.dto.EnqueteDto;
import jp.agentec.abook.abv.bl.dto.GroupDto;
import jp.agentec.abook.abv.bl.dto.ServiceOptionDto;
import jp.agentec.abook.abv.bl.dto.SppDeviceDto;
import jp.agentec.abook.abv.bl.logic.AbstractLogic;
import jp.agentec.abook.abv.bl.logic.CategoryLogic;
......@@ -47,9 +57,22 @@ import jp.agentec.adf.util.DateTimeUtil;
* FIXME: DBのトランザクションができていない
*
*/
public class ContentRefresher {
public class ContentRefresher {
private static final String TAG = "ContentRefresher";
private static ContentRefresher instance;
private static ContentRefresher instance;
/**
* 契約オプション・グループ・カテゴリの ACMS 取得を並列化する(DB 反映は取得完了後に従来順で直列)。
*/
private static final ExecutorService REFRESH_METADATA_FETCH_POOL = Executors.newFixedThreadPool(3, new ThreadFactory() {
private final AtomicInteger seq = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "RefreshMetaFetch-" + seq.getAndIncrement());
t.setDaemon(true);
return t;
}
});
private ContractLogic contractLogic = AbstractLogic.getLogic(ContractLogic.class);
private GroupLogic groupLogic = AbstractLogic.getLogic(GroupLogic.class);
......@@ -165,9 +188,7 @@ import jp.agentec.adf.util.DateTimeUtil;
List<ContentDto> localContents = contentDao.getAllContents();
if (networkAdapter.isNetworkConnected()) { // オンライン時の処理
resendLog(); // ログ送信
contractLogic.initializeContractServiceOption(); // サービスオプション関連処理
groupLogic.initializeGroups(); // グループ設定(グループ変更の場合、FetchDateをクリアする)
categoryLogic.initializeCategories(); // カテゴリ設定
runParallelContractGroupCategoryFetchAndApply();
// マスタデータの最新更新された時のFetchDateを一時に保存する。
Logger.d(TAG, "before fetchDate : " + ABVDataCache.getInstance().tempMasterDataFetchDate);
......@@ -222,6 +243,42 @@ import jp.agentec.adf.util.DateTimeUtil;
Logger.i(TAG, "refresh main thread end.-----------------------------------------------");
}
/**
* サービスオプション・グループ・カテゴリの HTTP を並列化し、完了後に DB へ従来順(契約→グループ→カテゴリ)で反映する。
*/
private void runParallelContractGroupCategoryFetchAndApply() throws Exception {
// interrupt は従来も契約/グループ/カテゴリの各処理の間には挟まないため、ここでも取得後の反映を省略しない
MemberInfoDao memberInfoDao = AbstractDao.getDao(MemberInfoDao.class);
final String categoryLastUpdate = memberInfoDao.getLastUpdateInfor();
Future<List<ServiceOptionDto>> fOpt = REFRESH_METADATA_FETCH_POOL.submit(new Callable<List<ServiceOptionDto>>() {
@Override
public List<ServiceOptionDto> call() throws Exception {
return contractLogic.fetchServiceOptionsOnly();
}
});
Future<List<GroupDto>> fGrp = REFRESH_METADATA_FETCH_POOL.submit(new Callable<List<GroupDto>>() {
@Override
public List<GroupDto> call() throws Exception {
return groupLogic.fetchGroupsOnly();
}
});
Future<CategoriesJSON> fCat = REFRESH_METADATA_FETCH_POOL.submit(new Callable<CategoriesJSON>() {
@Override
public CategoriesJSON call() throws Exception {
return categoryLogic.fetchCategoriesJson(categoryLastUpdate);
}
});
// 取得完了の都度すぐ反映する(従来は契約→グループ→カテゴリの順で、前段のDB反映後に次のHTTPが走っていた。失敗時のローカル状態もそれに合わせる)
List<ServiceOptionDto> opts = fOpt.get();
contractLogic.applyFetchedServiceOptions(opts);
List<GroupDto> grps = fGrp.get();
groupLogic.applyFetchedGroups(grps);
CategoriesJSON catJson = fCat.get();
categoryLogic.applyFetchedCategories(catJson, categoryLastUpdate);
}
/**
* ローカルコンテンツの公開期限や権限喪失のチェックを行う
* @param localContents List<ContentDto> 新着や更新対象のチェックが終わったコンテンツリスト
......
package jp.agentec.abook.abv.bl.logic;
import java.util.ArrayList;
import java.util.List;
import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.acms.client.json.CategoriesJSON;
import jp.agentec.abook.abv.bl.acms.client.parameters.AcmsParameters;
import jp.agentec.abook.abv.bl.acms.client.parameters.CategoriesParameters;
import jp.agentec.abook.abv.bl.common.exception.ABVException;
import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.data.dao.CategoryDao;
import jp.agentec.abook.abv.bl.data.dao.MemberInfoDao;
......@@ -25,16 +25,24 @@ public class CategoryLogic extends AbstractLogic {
*/
public void initializeCategories() throws Exception {
MemberInfoDao memberInfoDao = AbstractDao.getDao(MemberInfoDao.class);
AcmsParameters param;
String lastUpdateDate = memberInfoDao.getLastUpdateInfor();
param = new CategoriesParameters(cache.getMemberInfo().sid, lastUpdateDate);
CategoriesJSON json = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).category(param);
if (lastUpdateDate == null) {
initializeCategories(json);
} else {
initializeCategories(json, lastUpdateDate);
}
//cache.refreshCategory(dao.getCategories(CategoryDto.class));
CategoriesJSON json = fetchCategoriesJson(lastUpdateDate);
applyFetchedCategories(json, lastUpdateDate);
}
/** カテゴリ API の取得のみ(DB は更新しない)。refresh 時の並列フェッチ用。 */
public CategoriesJSON fetchCategoriesJson(String lastUpdateDate) throws NetworkDisconnectedException, AcmsException {
AcmsParameters param = new CategoriesParameters(cache.getMemberInfo().sid, lastUpdateDate);
return AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).category(param);
}
/** {@link #fetchCategoriesJson(String)} の結果をローカルに反映する。 */
public void applyFetchedCategories(CategoriesJSON json, String lastUpdateDate) throws Exception {
if (lastUpdateDate == null) {
initializeCategories(json);
} else {
initializeCategories(json, lastUpdateDate);
}
}
private void initializeCategories(CategoriesJSON json, String lastUpdate) throws Exception {
......@@ -47,6 +55,7 @@ public class CategoryLogic extends AbstractLogic {
categoryDao.beginTransaction();
if (json.categoryList != null && !json.categoryList.isEmpty()) {
boolean categoryRowChanged = false;
for (CategoryDto dto: json.categoryList) {
// 未指定ジャンルIDをキャッシュに入れておく
if (dto.dispOrder == -1) {
......@@ -56,15 +65,20 @@ public class CategoryLogic extends AbstractLogic {
//lastUpdateの情報がないということはデータベースにジャンルデータがないのでジャンルが存在するかどうかのチェックは不要
if (StringUtil.isNullOrEmpty(lastUpdate)) {
categoryDao.insertCategory(dto.categoryId, dto.parentCategoryId, dto.dispOrder, dto.categoryName);
categoryRowChanged = true;
} else {
CategoryDto categoryDto = categoryDao.getCategory(dto.categoryId);
if (categoryDto == null) {
categoryDao.insertCategory(dto.categoryId, dto.parentCategoryId, dto.dispOrder, dto.categoryName);
} else {
categoryRowChanged = true;
} else if (!isSameCategoryRow(dto, categoryDto)) {
categoryDao.updateCategory(dto.categoryId, dto.parentCategoryId, dto.dispOrder, dto.categoryName, categoryDto.parentCategoryId);
categoryRowChanged = true;
}
}
// category_pathを新たに作り直す。
}
// category_pathを新たに作り直す(各ジャンルごとではなく一括で十分)
if (categoryRowChanged) {
categoryDao.regenerateCategoryPath();
}
}
......@@ -114,8 +128,10 @@ public class CategoryLogic extends AbstractLogic {
exist = serverDto.equals(localDto);
if (exist) {
// update
categoryDao.updateCategory(serverDto.categoryId, serverDto.parentCategoryId, serverDto.dispOrder, serverDto.categoryName, localDto.parentCategoryId);
// update(サーバJSONに同一ジャンルが重複しても無駄なUPDATEを避ける)
if (!isSameCategoryRow(serverDto, localDto)) {
categoryDao.updateCategory(serverDto.categoryId, serverDto.parentCategoryId, serverDto.dispOrder, serverDto.categoryName, localDto.parentCategoryId);
}
// 更新したグループはローカルのリストから外しておく。
localCategories.remove(localDto);
......@@ -133,10 +149,6 @@ public class CategoryLogic extends AbstractLogic {
// category_pathを新たに作り直す。
categoryDao.regenerateCategoryPath();
for (CategoryDto serverDto : serverCategories) {
categoryDao.updateCategory(serverDto.categoryId, serverDto.parentCategoryId, serverDto.dispOrder, serverDto.categoryName, serverDto.parentCategoryId);
}
if (localCategories != null) {
for (CategoryDto localDto : localCategories) {
// delete
......@@ -160,4 +172,17 @@ public class CategoryLogic extends AbstractLogic {
memberInfoDao.updateLastUpdateInfor(lastUpdateDate);
}
/** 親・表示順・名称が一致すればDBのUPDATEは不要(category_pathはregenerateで揃う) */
private static boolean isSameCategoryRow(CategoryDto a, CategoryDto b) {
return a.parentCategoryId == b.parentCategoryId
&& a.dispOrder == b.dispOrder
&& sameText(a.categoryName, b.categoryName);
}
private static boolean sameText(String x, String y) {
String nx = x == null ? "" : x;
String ny = y == null ? "" : y;
return nx.equals(ny);
}
}
......@@ -43,16 +43,17 @@ public class ContractLogic extends AbstractLogic {
}
/**
* サービスオプション情報をローカルに保存します。この際、既存のデータがあれば削除してから新規保存します。
* @throws NetworkDisconnectedException
* @throws AcmsException
* @throws Exception その他、例外です。
* @since 1.0.0
* サービスオプションの取得のみ(DBは更新しない)。refresh 時の並列フェッチ用。
*/
public void initializeContractServiceOption() throws NetworkDisconnectedException, AcmsException {
public List<ServiceOptionDto> fetchServiceOptionsOnly() throws NetworkDisconnectedException, AcmsException {
AcmsParameters param = new AcmsParameters(cache.getMemberInfo().sid);
List<ServiceOptionDto> list = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).serviceOption(param);
return AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).serviceOption(param);
}
/**
* {@link #fetchServiceOptionsOnly()} の結果をローカルに反映する。
*/
public void applyFetchedServiceOptions(List<ServiceOptionDto> list) throws NetworkDisconnectedException, AcmsException {
if (list != null && list.size() > 0) {
if (isSameServiceOptionListAsDb(list)) {
Logger.d(TAG, "service options unchanged, skip delete/insert");
......@@ -60,10 +61,10 @@ public class ContractLogic extends AbstractLogic {
}
try {
serviceOptionDao.beginTransaction();
serviceOptionDao.deleteServiceOptions();
serviceOptionDao.insertServiceOptions(list);
serviceOptionDao.commit();
cache.addServiceOptions(list);
} catch (Exception e) {
......@@ -75,6 +76,17 @@ public class ContractLogic extends AbstractLogic {
}
/**
* サービスオプション情報をローカルに保存します。この際、既存のデータがあれば削除してから新規保存します。
* @throws NetworkDisconnectedException
* @throws AcmsException
* @throws Exception その他、例外です。
* @since 1.0.0
*/
public void initializeContractServiceOption() throws NetworkDisconnectedException, AcmsException {
applyFetchedServiceOptions(fetchServiceOptionsOnly());
}
/**
* サーバ取得のサービスオプションとローカルDBの内容が同一なら、削除・再挿入を省略する。
*/
private boolean isSameServiceOptionListAsDb(List<ServiceOptionDto> serverList) {
......
......@@ -40,13 +40,16 @@ public class GroupLogic extends AbstractLogic {
* @throws Exception その他、例外です。
* @since 1.0.0
*/
public void initializeGroups() throws NetworkDisconnectedException, AcmsException {
public List<GroupDto> fetchGroupsOnly() throws NetworkDisconnectedException, AcmsException {
AcmsParameters param = new AcmsParameters(cache.getMemberInfo().sid);
List<GroupDto> serverGroups = AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).group(param);
return AcmsClient.getInstance(cache.getUrlPath(), networkAdapter).group(param);
}
public void applyFetchedGroups(List<GroupDto> serverGroups) throws NetworkDisconnectedException, AcmsException {
ContentGroupDao contentGroupDao = AbstractDao.getDao(ContentGroupDao.class);
if (serverGroups == null || serverGroups.size() == 0) {
Logger.w(TAG, "Group Data is Nothing");
Logger.w(TAG, "Group Data is Nothing");
return;
}
try {
......@@ -127,6 +130,10 @@ public class GroupLogic extends AbstractLogic {
} finally {
}
}
public void initializeGroups() throws NetworkDisconnectedException, AcmsException {
applyFetchedGroups(fetchGroupsOnly());
}
//해당 콘텐츠 아이디를 가지고 있는 그룹정보
public ArrayList<String>getExistContentsGroupInfo(long contentId) {
......
......@@ -34,10 +34,15 @@ public class OperationGroupMasterLogic extends AbstractLogic {
* 作業種別・作業種別に紐づいた作業IDを取得して
* DBに登録する
*/
public void setOperationGroupMaster() throws NetworkDisconnectedException, AcmsException {
public List<OperationGroupMasterDto> fetchOperationGroupMasterOnly() throws NetworkDisconnectedException, AcmsException {
return getOperationGroupMasterServerData();
}
public void applyFetchedOperationGroupMaster(List<OperationGroupMasterDto> serverOperationGroupMasterDtos) {
try {
// サーバー通信で、作業種別情報を取得する
List<OperationGroupMasterDto> serverOperationGroupMasterDtos = getOperationGroupMasterServerData();
if (serverOperationGroupMasterDtos == null) {
return;
}
// ローカルDBに存在する作業種別情報をDto配列でセット
List<OperationGroupMasterDto> localOperationGroupMasterDtos = mOperationGroupMasterDao.getAllOperationGroupMaster();
List<Integer> localOperationGroupMasterIds = new ArrayList<Integer>();
......@@ -83,6 +88,10 @@ public class OperationGroupMasterLogic extends AbstractLogic {
}
}
public void setOperationGroupMaster() throws NetworkDisconnectedException, AcmsException {
applyFetchedOperationGroupMaster(fetchOperationGroupMasterOnly());
}
/**
* API通信で取得した作業種別情報のリストを返す
* @return
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment