Java tutorial
/* * Copyright (C) 2012-2013 NS Solutions Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.htmlhifive.sync.resource; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.PropertyAccessorFactory; import com.htmlhifive.resourcefw.config.MessageMetadata; import com.htmlhifive.resourcefw.exception.AbstractResourceException; import com.htmlhifive.resourcefw.exception.BadRequestException; import com.htmlhifive.resourcefw.exception.ConflictException; import com.htmlhifive.resourcefw.exception.GenericResourceException; import com.htmlhifive.resourcefw.exception.LockedException; import com.htmlhifive.resourcefw.exception.NotFoundException; import com.htmlhifive.resourcefw.exception.NotModifiedException; import com.htmlhifive.resourcefw.exception.ServiceUnavailableException; import com.htmlhifive.resourcefw.message.RequestMessage; import com.htmlhifive.resourcefw.message.RequestMessageUtil; import com.htmlhifive.resourcefw.resource.AbstractCrudResource; import com.htmlhifive.sync.config.SyncConfigurationParameter; import com.htmlhifive.sync.exception.SyncConflictException; import com.htmlhifive.sync.exception.SyncDuplicateIdConflictException; import com.htmlhifive.sync.exception.SyncUpdateConflictException; import com.htmlhifive.sync.resource.common.ResourceItemCommonData; import com.htmlhifive.sync.resource.common.ResourceItemCommonDataId; import com.htmlhifive.sync.resource.common.SyncAction; import com.htmlhifive.sync.resource.update.UpdateStrategy; import com.htmlhifive.sync.service.SyncRequestCommonData; /** * JPA Entity?CRUD?sync?.<br/> * ?()??sync??????.<br/> * ????????????????????????. * * @author kishigam * @param <T>(JPA Entity) */ public abstract class AbstractCrudSyncResource<T> extends AbstractCrudResource<T> implements SyncResource { /** * ?(Map)??? */ private static final String DOWNLOAD_RESULT_ITEM_KEY = "item"; /** * ?(Map)??? */ private static final String DOWNLOAD_RESULT_COMMON_DATA_KEY = "resourceItemCommonData"; /** * sync??????synchronizer. */ private Synchronizer synchronizer; /** * ???. */ private UpdateStrategy updateStrategy; /** * {@link Synchronizer Synchronizer}??????.<br/> * ???sync?????????????.<br/> * ??????{@link SyncConflictException}???. * * @param requestMessage * @return ? */ public Object upload(RequestMessage requestMessage) throws BadRequestException, ServiceUnavailableException, LockedException, NotFoundException, SyncConflictException { // ID?? ResourceItemCommonDataId resourceItemIdObj = (ResourceItemCommonDataId) requestMessage .get(synchronizer.getSyncConfigurationParameter().RESOURCE_ITEM_COMMON_DATA_ID); if (resourceItemIdObj.getResourceItemId() == null || resourceItemIdObj.getResourceItemId().isEmpty()) { // ????ResourceExceptionHandler???? throw new GenericResourceException( new BadRequestException("Resource item id is needed.", requestMessage)); } // ?? ResourceItemCommonData clientItemCommon = createResourceItemCommonData(requestMessage); ResourceItemCommonData currentItemCommon = getCurrentItemCommon(requestMessage); switch (clientItemCommon.getSyncAction()) { case CREATE: currentItemCommon = doCreate(requestMessage, clientItemCommon, currentItemCommon); break; case UPDATE: case DELETE: currentItemCommon = doUpdateOrDelete(requestMessage, clientItemCommon, currentItemCommon); break; default: throw new GenericResourceException("Unknown sync action."); } // sync? synchronizer.modify(currentItemCommon); return currentItemCommon; } /** * ?????????????. * * @param requestMessage * @return */ private ResourceItemCommonData createResourceItemCommonData(RequestMessage requestMessage) throws BadRequestException { SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); ResourceItemCommonData common = new ResourceItemCommonData( (ResourceItemCommonDataId) requestMessage.get(configParam.RESOURCE_ITEM_COMMON_DATA_ID)); common.setTargetItemId(getId(requestMessage)); // syncAction?? String syncActionStr = (String) requestMessage.get(configParam.SYNC_ACTION); if (!isSyncAction(syncActionStr)) { throw new BadRequestException("No such sync action. : sync action = " + syncActionStr, requestMessage); } common.setSyncAction(SyncAction.valueOf(syncActionStr)); Object lastModifiedObj = requestMessage.get(configParam.LAST_MODIFIED); if (lastModifiedObj != null) { common.setLastModified(Long.parseLong((String) lastModifiedObj)); } return common; } /** * ??SyncAction????true???. * * @param syncActionStr syncAction? * @return SyncAction???true */ private boolean isSyncAction(String syncActionStr) { if (syncActionStr == null) return false; for (SyncAction syncAction : EnumSet.allOf(SyncAction.class)) { if (syncAction.toString().equals(syncActionStr)) return true; } return false; } /** * ???????????.<br/> * ?????????????????????????.<br/> * ????????. * * @param requestMessage * @return */ @SuppressWarnings("unchecked") private ResourceItemCommonData getCurrentItemCommon(RequestMessage requestMessage) throws SyncConflictException, NotFoundException, BadRequestException, LockedException { SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); ResourceItemCommonData currentItemCommon; // CREATE???? SyncAction syncAction = SyncAction.valueOf((String) requestMessage.get(configParam.SYNC_ACTION)); if (syncAction == SyncAction.CREATE) { ResourceItemCommonDataId commonDataId = (ResourceItemCommonDataId) requestMessage .get(configParam.RESOURCE_ITEM_COMMON_DATA_ID); currentItemCommon = synchronizer.getNew(commonDataId); // ?ID??? if (currentItemCommon.getSyncAction() == SyncAction.DUPLICATE) { throw new SyncDuplicateIdConflictException(currentItemCommon.getSyncAction().toString(), findById(requestMessage), configParam, requestMessage); } return currentItemCommon; } // CREATE?? // ???getForUpdate?????? Object gotCommonDataListObj = requestMessage.get(configParam.RESOURCE_ITEM_COMMON_DATA); if (gotCommonDataListObj != null) { List<ResourceItemCommonData> commonList = (List<ResourceItemCommonData>) gotCommonDataListObj; // ???NotFound if (commonList.isEmpty()) { throw new NotFoundException("Sync target resource item is not found.", requestMessage); } return commonList.get(0); } // ???????????? ResourceItemCommonDataId commonDataId = (ResourceItemCommonDataId) requestMessage .get(configParam.RESOURCE_ITEM_COMMON_DATA_ID); currentItemCommon = synchronizer.getForUpdate(commonDataId); // ???NotFound if (currentItemCommon == null) { throw new NotFoundException("Sync target resource item is not found.", requestMessage); } return currentItemCommon; } /** * ??????????????.<br/> * ?????????{@link SyncConflictException}???. * * @param requestMessage * @param clientItemCommon ??? * @param currentItemCommon ?????? * @return */ private ResourceItemCommonData doCreate(RequestMessage requestMessage, ResourceItemCommonData clientItemCommon, ResourceItemCommonData currentItemCommon) throws BadRequestException, ServiceUnavailableException, LockedException, NotFoundException, SyncConflictException { SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); SyncRequestCommonData requestCommon = (SyncRequestCommonData) requestMessage .get(configParam.REQUEST_COMMON_DATA); String targetItemId = null; try { targetItemId = insert(requestMessage); } catch (ConflictException e) { // sync?ID??? throw new SyncDuplicateIdConflictException(e, findById(requestMessage), configParam, requestMessage); } currentItemCommon.setTargetItemId(targetItemId); currentItemCommon.modify(clientItemCommon.getSyncAction(), requestCommon.getSyncTime()); return currentItemCommon; } /** * ?????????????.<br/> * ?????????{@link UpdateStrategy}?????.<br/> * ????????????????. * * @param requestMessage * @param clientItemCommon ??? * @param currentItemCommon ?????? * @return */ private ResourceItemCommonData doUpdateOrDelete(RequestMessage requestMessage, ResourceItemCommonData clientItemCommon, ResourceItemCommonData currentItemCommon) throws BadRequestException, NotFoundException, LockedException, SyncConflictException { SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); SyncRequestCommonData requestCommon = (SyncRequestCommonData) requestMessage .get(configParam.REQUEST_COMMON_DATA); // ??? SyncAction resolvedSyncAction = clientItemCommon.getSyncAction(); if (synchronizer.isConflicted(clientItemCommon, currentItemCommon, requestCommon)) { T clientItem = RequestMessageUtil.extractObject(requestMessage, getItemType(), getIdFieldName(), getId(requestMessage)); Object currentItem = findById(requestMessage); // UpdateStrategy??????????getUpdateStrategy??? resolvedSyncAction = getUpdateStrategy().resolveConflict(clientItemCommon, clientItem, currentItemCommon, currentItem); if (resolvedSyncAction == SyncAction.CONFLICT) { throw new SyncUpdateConflictException(resolvedSyncAction.toString(), currentItem, configParam, requestMessage); } } // ??????? switch (resolvedSyncAction) { case NONE: break; case UPDATE: update(requestMessage); break; case DELETE: remove(requestMessage); break; default: throw new GenericResourceException("Unknown resolved SyncAction. : " + resolvedSyncAction); } currentItemCommon.modify(resolvedSyncAction, requestCommon.getSyncTime()); return currentItemCommon; } /** * {@link Synchronizer Synchronizer}??????.<br/> * ??????????????????????????. * * @param requestMessage * @return ?? * @throws AbstractResourceException */ public Object download(RequestMessage requestMessage) throws AbstractResourceException { SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); // lastModified?????????? long modifiedSince = 0L; Object lastModifiedObj = requestMessage.get(configParam.LAST_MODIFIED); try { if (lastModifiedObj != null) { modifiedSince = Long.parseLong((String) lastModifiedObj); } } catch (NumberFormatException e) { throw new BadRequestException("Failed to parse Last modified time. : " + (String) lastModifiedObj, requestMessage); } String resourceName = ((ResourceItemCommonDataId) requestMessage .get(configParam.RESOURCE_ITEM_COMMON_DATA_ID)).getResourceName(); MessageMetadata messageMetadata = requestMessage.getMessageMetadata(); // query???downloadByQuery,???downloadById Object query = requestMessage.get(messageMetadata.QUERY); if (query == null) { return downloadById(resourceName, modifiedSince, requestMessage); } return downloadByQuery(resourceName, modifiedSince, requestMessage); } /** * ID??????.<br/> * ???????????????????{@link NotModifiedException}????. * * @param resourceName ?? * @param modifiedSince ? * @param requestMessage * @return ?? * @throws AbstractResourceException */ @SuppressWarnings("unchecked") private Object downloadById(String resourceName, long modifiedSince, RequestMessage requestMessage) throws AbstractResourceException { // ????NotFound if (!(boolean) exists(requestMessage).get("exists")) { throw new NotFoundException("Sync target resource item is not found.", requestMessage); } SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); List<ResourceItemCommonData> commonList; Object gotCommonDataListObj = requestMessage.get(configParam.RESOURCE_ITEM_COMMON_DATA); if (gotCommonDataListObj != null) { // ??getForUpdate?????? commonList = (List<ResourceItemCommonData>) gotCommonDataListObj; } else { // ???????? List<String> targetItemIdList = new ArrayList<>(); targetItemIdList.add(getId(requestMessage)); commonList = synchronizer.getModified(resourceName, targetItemIdList, modifiedSince); } // modified????NotModified if (commonList.isEmpty()) { throw new NotModifiedException(requestMessage); } // ??????Object(Map) Map<String, Object> result = new HashMap<>(); ResourceItemCommonData common = commonList.get(0); result.put(DOWNLOAD_RESULT_COMMON_DATA_KEY, common); // ????ID???? if (common.getSyncAction() == SyncAction.DELETE) { result.put(DOWNLOAD_RESULT_ITEM_KEY, createDeletedItem(common)); } else { // ???? // findById?ID?list???????????? // ???????? result.put(DOWNLOAD_RESULT_ITEM_KEY, findById(requestMessage)); } return result; } /** * ID??????.<br/> * ?????????????????????.<br/> * * @param resourceName ?? * @param modifiedSince ? * @param requestMessage * @return ??? */ @SuppressWarnings("unchecked") private Object downloadByQuery(String resourceName, long modifiedSince, RequestMessage requestMessage) throws BadRequestException, LockedException, NotModifiedException, NotFoundException { SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); List<ResourceItemCommonData> modifiedCommonList; Object gotCommonDataListObj = requestMessage.get(configParam.RESOURCE_ITEM_COMMON_DATA); if (gotCommonDataListObj != null) { // ??getForUpdate?????? modifiedCommonList = (List<ResourceItemCommonData>) gotCommonDataListObj; } else { // modified???????ID?? List<String> targetItemIdList = new ArrayList<>(); Object itemObj = findByQuery(requestMessage); // ???????????? if (!(itemObj instanceof List)) { return itemObj; } List<?> itemObjList = (List<?>) itemObj; if (itemObjList.isEmpty() || !getItemType().isAssignableFrom(itemObjList.get(0).getClass())) { return itemObj; } for (Object item : itemObjList) { targetItemIdList.add(getIdFieldValue(getItemType().cast(item))); } modifiedCommonList = synchronizer.getModified(resourceName, targetItemIdList, modifiedSince); } // ??????Object(Map)?List ArrayList<Map<String, Object>> resultList = new ArrayList<>(); for (ResourceItemCommonData common : modifiedCommonList) { Map<String, Object> result = new HashMap<>(); result.put(DOWNLOAD_RESULT_COMMON_DATA_KEY, common); // ????ID???? if (common.getSyncAction() == SyncAction.DELETE) { result.put(DOWNLOAD_RESULT_ITEM_KEY, createDeletedItem(common)); } else { // ID?RequestMessage????????? T item = getRepository().findOne(common.getTargetItemId()); result.put(DOWNLOAD_RESULT_ITEM_KEY, item); } resultList.add(result); } return resultList; } /** * ???ID???????. * * @param common ? * @return */ private T createDeletedItem(ResourceItemCommonData common) { Class<T> itemType = getItemType(); BeanWrapper deletedWrapper = PropertyAccessorFactory .forBeanPropertyAccess(BeanUtils.instantiateClass(itemType)); deletedWrapper.setPropertyValue(getIdFieldName(), common.getTargetItemId()); return itemType.cast(deletedWrapper.getWrappedInstance()); } /** * ???????. * * @param requestMessage * @return ? * @throws AbstractResourceException */ @Override public List<ResourceItemCommonData> getForUpdate(RequestMessage requestMessage) throws AbstractResourceException { SyncConfigurationParameter configParam = synchronizer.getSyncConfigurationParameter(); // lastModified?????????? long modifiedSince = 0L; Object lastModifiedObj = requestMessage.get(configParam.LAST_MODIFIED); try { if (lastModifiedObj != null) { modifiedSince = Long.parseLong((String) lastModifiedObj); } } catch (NumberFormatException e) { throw new BadRequestException("Failed to parse Last modified time. : " + (String) lastModifiedObj, requestMessage); } String resourceName = ((ResourceItemCommonDataId) requestMessage .get(configParam.RESOURCE_ITEM_COMMON_DATA_ID)).getResourceName(); MessageMetadata messageMetadata = requestMessage.getMessageMetadata(); Object query = requestMessage.get(messageMetadata.QUERY); if (query == null) { return getByIdForUpdate(resourceName, modifiedSince, requestMessage); } return getByQueryForUpdate(resourceName, modifiedSince, requestMessage); } /** * ID????????.<br/> * ???????????????????null???? * * @param resourceName ?? * @param modifiedSince ? * @param requestMessage * @return * @throws AbstractResourceException */ private List<ResourceItemCommonData> getByIdForUpdate(String resourceName, long modifiedSince, RequestMessage requestMessage) throws AbstractResourceException { List<String> targetItemIdList = new ArrayList<>(); // ID?? targetItemIdList.add(getId(requestMessage)); // 1 return synchronizer.getModifiedForUpdate(resourceName, targetItemIdList, modifiedSince); } /** * ID??????.<br/> * ?????????????????????.<br/> * * @param resourceName ?? * @param modifiedSince ? * @param requestMessage * @return ? * @throws AbstractResourceException */ private List<ResourceItemCommonData> getByQueryForUpdate(String resourceName, long modifiedSince, RequestMessage requestMessage) throws AbstractResourceException { Object itemObj = findByQuery(requestMessage); // ??????? if (!(itemObj instanceof List)) { return Collections.emptyList(); } List<?> itemObjList = (List<?>) itemObj; if (itemObjList.isEmpty() || !getItemType().isAssignableFrom(itemObjList.get(0).getClass())) { return Collections.emptyList(); } List<String> targetItemIdList = new ArrayList<>(); for (Object item : itemObjList) { targetItemIdList.add(getIdFieldValue(getItemType().cast(item))); } return synchronizer.getModifiedForUpdate(resourceName, targetItemIdList, modifiedSince); } /** * ??????. * * @return synchronizer */ @Override public Synchronizer getSynchronizer() { return this.synchronizer; } /** * ??????????. */ @Override public void setSynchronizer(Synchronizer synchronizer) { this.synchronizer = synchronizer; } @Override public UpdateStrategy getUpdateStrategy() { if (this.updateStrategy == null) return this.synchronizer.getDefaultUpdateStrategy(); return updateStrategy; } @Override public void setUpdateStrategy(UpdateStrategy updateStrategy) { this.updateStrategy = updateStrategy; } }