/* * Copyright 2012 Kazumune Katagiri. (http://d.hatena.ne.jp/nemuzuka) * * 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 jp.co.nemuzuka.service.impl; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import jp.co.nemuzuka.common.PeriodStatus; import jp.co.nemuzuka.dao.TicketDao; import jp.co.nemuzuka.dao.TicketDao.Param; import jp.co.nemuzuka.entity.TicketModelEx; import jp.co.nemuzuka.exception.NotExistTicketException; import jp.co.nemuzuka.exception.ParentSelfTicketException; import jp.co.nemuzuka.form.ProjectForm; import jp.co.nemuzuka.form.TicketCommentForm; import jp.co.nemuzuka.form.TicketDetailForm; import jp.co.nemuzuka.form.TicketForm; import jp.co.nemuzuka.model.MemberModel; import jp.co.nemuzuka.model.TicketModel; import jp.co.nemuzuka.service.CommentService; import jp.co.nemuzuka.service.MemberService; import jp.co.nemuzuka.service.ProjectService; import jp.co.nemuzuka.service.TicketMstService; import jp.co.nemuzuka.service.TicketService; import jp.co.nemuzuka.service.UploadFileService; import jp.co.nemuzuka.utils.ConvertUtils; import jp.co.nemuzuka.utils.CurrentDateUtils; import jp.co.nemuzuka.utils.DateTimeUtils; import org.apache.commons.lang.StringUtils; import org.slim3.datastore.Datastore; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.Text; /** * TicketServiceの実装クラス. * @author k-katagiri */ public class TicketServiceImpl implements TicketService { TicketDao ticketDao = TicketDao.getInstance(); ProjectService projectService = ProjectServiceImpl.getInstance(); CommentService commentService = CommentServiceImpl.getInstance(); TicketMstService ticketMstService = TicketMstServiceImpl.getInstance(); MemberService memberService = MemberServiceImpl.getInstance(); UploadFileService uploadFileService = UploadFileServiceImpl.getInstance(); private static TicketServiceImpl impl = new TicketServiceImpl(); /** * インスタンス取得. * @return インスタンス */ public static TicketServiceImpl getInstance() { return impl; } /** * コンストラクタ. */ private TicketServiceImpl(){} /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#getList(jp.co.nemuzuka.dao.TicketDao.Param, java.lang.String, boolean) */ @Override public List<TicketModelEx> getList(Param param, String mail, boolean isDashboard) { List<TicketModel> modelList = null; if(isDashboard) { String memberKeyString = memberService.getKeyString(mail); modelList = ticketDao.getDashbordList(param.limit, memberKeyString, param.projectKeyString, param.openStatus); } else { modelList = ticketDao.getList(param); } //プロジェクト情報を取得 ProjectForm projectForm = projectService.get(param.projectKeyString); if(StringUtils.isEmpty(projectForm.keyToString)) { //存在しない場合、一覧はサイズ0のTicket return new ArrayList<TicketModelEx>(); } String projectId = projectForm.projectId; //登録されている担当者情報を取得 Set<Key> memberKeySet = new LinkedHashSet<Key>(); for(TicketModel target : modelList) { if(target.getTargetMemberKey() != null) { memberKeySet.add(target.getTargetMemberKey()); } } Map<Key, MemberModel> memberMap = null; if(memberKeySet.size() != 0) { memberMap = memberService.getMap(memberKeySet.toArray(new Key[0])); } else { memberMap = new HashMap<Key, MemberModel>(); } //戻りListを作成 Date today = CurrentDateUtils.getInstance().getCurrentDate(); SimpleDateFormat sdf = DateTimeUtils.createSdf("yyyyMMdd"); SimpleDateFormat sdf2 = DateTimeUtils.createSdf("yyyy/MM/dd HH:mm"); List<TicketModelEx> retList = new ArrayList<TicketModelEx>(); for(TicketModel target : modelList) { TicketModelEx entity = new TicketModelEx(); entity.setModel(target); entity.setStartDate(ConvertUtils.toString(target.getStartDate(), sdf)); entity.setPeriod(ConvertUtils.toString(target.getPeriod(), sdf)); entity.setCreatedAt(ConvertUtils.toString(target.getCreatedAt(), sdf2)); //期限が設定されている場合 if(target.getPeriod() != null) { entity.setPeriodStatus( createPeriodStatus(today, target.getPeriod(), target.getStatus(), param.openStatus)); } //担当者が設定されている場合 if(target.getTargetMemberKey() != null) { MemberModel member = memberMap.get(target.getTargetMemberKey()); if(member != null) { entity.setTargetMemberName(member.getName()); } } //ステータスが完了しているかを設定 entity.setCloseStatus(isCloseStatus(target.getStatus(), param.openStatus)); //Noを表示する if(StringUtils.isNotEmpty(projectId)) { entity.setViewNo(projectId + "-" + target.getNo()); } else { entity.setViewNo(String.valueOf(target.getNo())); } retList.add(entity); } return retList; } /** * ステータスがクローズ状態かを判断します。 * @param status ステータス * @param openStatus 未完了を意味するステータス配列 * @return ステータスがクローズ状態の場合、true */ private boolean isCloseStatus(String status, String[] openStatus) { if(openStatus == null || openStatus.length == 0) { return true; } for(String targetStatus : openStatus) { if(targetStatus.equals(status)) { return false; } } return true; } /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#get(java.lang.String, java.lang.String) */ @Override public TicketForm get(String keyString, String projectKeyString) { TicketForm form = new TicketForm(); if(StringUtils.isNotEmpty(keyString)) { //Key情報が設定されていた場合 Key key = Datastore.stringToKey(keyString); Key projectKey = Datastore.stringToKey(projectKeyString); TicketModel model = ticketDao.getWithProjectKey(key, projectKey); setForm(form, model); } ProjectForm projectForm = projectService.get(projectKeyString); form.setProjectId(projectForm.projectId); form.ticketMst = ticketMstService.getTicketMst(projectKeyString); return form; } /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#getDetail(java.lang.String, java.lang.String) */ @Override public TicketDetailForm getDetail(String keyString, String projectKeyString) { TicketForm form = get(keyString, projectKeyString); if(StringUtils.isEmpty(form.keyToString)) { return null; } TicketDetailForm detailForm = new TicketDetailForm(); detailForm.setForm(form); detailForm.commentList = commentService.getList(Datastore.stringToKey(form.keyToString)); detailForm.uploadFileList = uploadFileService.getList(keyString, projectKeyString); setTicketConn(detailForm, projectKeyString); return detailForm; } /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#put(jp.co.nemuzuka.form.TicketForm, java.lang.String) */ @Override public void put(TicketForm form, String projectKeyString) throws NotExistTicketException, ParentSelfTicketException { TicketModel model = null; Key projectKey = Datastore.stringToKey(projectKeyString); if(StringUtils.isNotEmpty(form.keyToString)) { //更新の場合 Key key = Datastore.stringToKey(form.keyToString); Long version = ConvertUtils.toLong(form.versionNo); //KeyとプロジェクトKeyとバージョンで取得 model = ticketDao.get(key, version, projectKey); if(model == null) { //該当レコードが存在しない場合、Exceptionをthrow throw new ConcurrentModificationException(); } } else { model = new TicketModel(); model.setProjectKey(projectKey); } setModel(model, form); ticketDao.put(model); } /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#delete(jp.co.nemuzuka.form.TicketForm, java.lang.String) */ @Override public void delete(TicketForm form, String projectKeyString) { Key projectKey = Datastore.stringToKey(projectKeyString); Key key = Datastore.stringToKey(form.keyToString); Long version = ConvertUtils.toLong(form.versionNo); //KeyとプロジェクトKeyとバージョンで取得 TicketModel model = ticketDao.get(key, version, projectKey); if(model == null) { //該当レコードが存在しない場合、Exceptionをthrow throw new ConcurrentModificationException(); } ticketDao.delete(key); } /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#updateTicketStatus(jp.co.nemuzuka.form.TicketForm, java.lang.String) */ @Override public void updateTicketStatus(TicketForm form, String projectKeyString) { Key projectKey = Datastore.stringToKey(projectKeyString); Key key = Datastore.stringToKey(form.keyToString); Long version = ConvertUtils.toLong(form.versionNo); //KeyとプロジェクトKeyとバージョンで取得 TicketModel model = ticketDao.get(key, version, projectKey); if(model == null) { //該当レコードが存在しない場合、Exceptionをthrow throw new ConcurrentModificationException(); } model.setStatus(form.status); ticketDao.put(model); } /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#putComment(jp.co.nemuzuka.form.TicketCommentForm, java.lang.String, java.lang.String) */ @Override public void putComment(TicketCommentForm form, String projectKeyString, String email) { //ステータスが変更されていないかチェックする Key ticketModelKey = Datastore.stringToKey(form.keyToString); Key projectKey = Datastore.stringToKey(projectKeyString); //Keyとプロジェクトでレコードを取得 TicketModel model = ticketDao.getWithProjectKey(ticketModelKey, projectKey); if(model == null) { //該当レコードが存在しない場合、Exceptionをthrow throw new ConcurrentModificationException(); } //ステータスが変更されているかチェックする if(model.getStatus().equals(form.status) == false) { Long versonNo = ConvertUtils.toLong(form.versionNo); //変更されているので、更新 if(model.getVersion().equals(versonNo) == false) { //他のユーザに更新されている可能性があるので、Exceptionをthrow throw new ConcurrentModificationException(); } model.setStatus(form.status); ticketDao.put(model); } //コメント登録 commentService.put(ticketModelKey, form.comment, email); } /* (non-Javadoc) * @see jp.co.nemuzuka.service.TicketService#deleteComment(java.lang.String, java.lang.String, java.lang.Long, java.lang.String) */ @Override public void deleteComment(String keyString, String commentKeyString, Long commentVersionNo, String projectKeyString) { Key ticketModelKey = Datastore.stringToKey(keyString); Key projectKey = Datastore.stringToKey(projectKeyString); //Keyとプロジェクトでレコードを取得 TicketModel model = ticketDao.getWithProjectKey(ticketModelKey, projectKey); if(model == null) { //該当レコードが存在しない場合、Exceptionをthrow throw new ConcurrentModificationException(); } //コメント削除 commentService.delete(ticketModelKey, commentKeyString, commentVersionNo); } /** * Form情報設定. * @param form 設定対象Form * @param model 設定Model */ private void setForm(TicketForm form, TicketModel model) { if(model == null) { return; } SimpleDateFormat sdf = DateTimeUtils.createSdf("yyyyMMdd"); form.keyToString = model.getKeyToString(); form.status = model.getStatus(); form.title = model.getTitle(); if(model.getContent() != null) { form.content = model.getContent().getValue(); } if(model.getEndCondition() != null) { form.endCondition = model.getEndCondition().getValue(); } form.startDate = ConvertUtils.toString(model.getStartDate(), sdf); form.period = ConvertUtils.toString(model.getPeriod(), sdf); form.priority = model.getPriority(); form.targetKind = model.getTargetKind(); form.category = model.getCategory(); if(model.getMilestone() != null) { form.milestone = Datastore.keyToString(model.getMilestone()); } form.targetVersion = model.getTargetVersion(); if(model.getTargetMemberKey() != null) { form.targetMember = Datastore.keyToString(model.getTargetMemberKey()); } if(model.getParentTicketKey() != null) { //親Keyに紐付くNoを変換して、設定 TicketModel parentModel = ticketDao.getWithProjectKey( model.getParentTicketKey(), model.getProjectKey()); if(parentModel != null) { form.parentKey = ConvertUtils.toString(parentModel.getNo()); } } form.versionNo = ConvertUtils.toString(model.getVersion()); form.id = ConvertUtils.toString(model.getNo()); } /** * Model情報設定. * @param model 設定対象Model * @param form 設定Form * @throws NotExistTicketException 親チケット指定時、存在しないチケットNoを指定された * @throws ParentSelfTicketException 親チケット指定時、自分のチケットNoを指定された */ private void setModel(TicketModel model, TicketForm form) throws NotExistTicketException, ParentSelfTicketException { SimpleDateFormat sdf = DateTimeUtils.createSdf("yyyyMMdd"); model.setTitle(form.title); model.setContent(new Text(StringUtils.defaultString(form.content))); model.setEndCondition(new Text(StringUtils.defaultString(form.endCondition))); model.setPriority(form.priority); model.setStatus(form.status); model.setTargetKind(form.targetKind); model.setCategory(form.category); Key milestoneKey = null; if(StringUtils.isNotEmpty(form.milestone)) { milestoneKey = Datastore.stringToKey(form.milestone); } model.setMilestone(milestoneKey); model.setTargetVersion(form.targetVersion); Key targetMemberKey = null; if(StringUtils.isNotEmpty(form.targetMember)) { targetMemberKey = Datastore.stringToKey(form.targetMember); } model.setTargetMemberKey(targetMemberKey); model.setStartDate(ConvertUtils.toDate(form.startDate, sdf)); model.setPeriod(ConvertUtils.toDate(form.period, sdf)); Key parentKey = null; if(StringUtils.isNotEmpty(form.parentKey)) { //入力されたNoに紐付くTicketを存在チェックし、存在なければ、Exception parentKey = ticketDao.getWithNoAndProjectKey( ConvertUtils.toLong(form.parentKey), model.getProjectKey()); if(parentKey == null) { throw new NotExistTicketException(); } if(parentKey.equals(model.getKey())) { //自分を参照する設定になっている場合、Exception throw new ParentSelfTicketException(); } } model.setParentTicketKey(parentKey); } /** * 期限ステータス判断. * @param today システム日付 * @param targetDate 期限 * @param status ステータス * @param openStatus 未完了を意味するステータス * @return 期限ステータス */ PeriodStatus createPeriodStatus(Date today, Date targetDate, String status, String[] openStatus) { long toDayTime = today.getTime(); long periodTime = targetDate.getTime(); Set<String> openStatsSet = new LinkedHashSet<String>(); for(String target : openStatus) { openStatsSet.add(target); } //ステータスが未完了の場合、システム日付と期限を参照して期限ステータスを返す PeriodStatus periodStatus = null; if(openStatsSet.contains(status)) { if(toDayTime == periodTime) { periodStatus = PeriodStatus.today; } else if(toDayTime > periodTime) { periodStatus = PeriodStatus.periodDate; } } return periodStatus; } /** * チケット関連情報取得. * @param detailForm 設定Form * @param projectKeyString プロジェクトKey文字列 */ private void setTicketConn(TicketDetailForm detailForm, String projectKeyString) { detailForm.childTicketList = new ArrayList<TicketModel>(); Key projectKey = Datastore.stringToKey(projectKeyString); Set<Key> targetKeySet = new LinkedHashSet<Key>(); //自分の親が設定されている場合 Key parentKey = null; if(StringUtils.isNotEmpty(detailForm.form.parentKey)) { //Noからデータを取得 parentKey = ticketDao.getWithNoAndProjectKey( ConvertUtils.toLong(detailForm.form.parentKey), projectKey); if(parentKey != null) { targetKeySet.add(parentKey); } } //自分が親になっているTicketを取得 Key self = Datastore.stringToKey(detailForm.form.keyToString); List<Key> childKeyList = ticketDao.getChildList(self, projectKey); targetKeySet.addAll(childKeyList); if(targetKeySet.size() == 0) { return; } //取得した関連Ticketを設定する Map<Key, TicketModel> ticketMap = ticketDao.getMap(projectKeyString, targetKeySet.toArray(new Key[0])); if(parentKey != null) { TicketModel parentTicket = ticketMap.get(parentKey); if(parentTicket != null) { detailForm.parentTicket = parentTicket; } } for(Key targetKey : childKeyList) { TicketModel targetModel = ticketMap.get(targetKey); if(targetModel != null) { detailForm.childTicketList.add(targetModel); } } } }