/**
* @Date: Feb 19, 2010 1:18:42 PM
*/
package com.philip.journal.home.service;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.philip.journal.common.BeanUtils;
import com.philip.journal.common.StringUtils;
import com.philip.journal.core.Constant;
import com.philip.journal.core.Messages;
import com.philip.journal.core.Messages.Error;
import com.philip.journal.core.bean.AbstractAuditableBean;
import com.philip.journal.core.bean.User;
import com.philip.journal.core.exception.JournalException;
import com.philip.journal.core.service.BaseService;
import com.philip.journal.home.bean.Branch;
import com.philip.journal.home.bean.ConfigItem;
import com.philip.journal.home.bean.Entry;
import com.philip.journal.home.dao.BranchDAO;
import com.philip.journal.home.dao.EntryDAO;
/**
* @author cry30
*/
public class HomeServiceImpl extends BaseService implements HomeService {
/** Used only by the getPreferences. Can be refactored out if getPreferences is moved to its own interface/impl. */
static final String CHILDREN = "children";
/** {@link #getBranchNodeProperty(StringBuilder, long)} or {@link #getEntryNodeProperty(StringBuilder, long)}. */
static final int IDX_BEAN = 0;
/** {@link #getBranchNodeProperty(StringBuilder, long)} or {@link #getEntryNodeProperty(StringBuilder, long)}. */
static final int IDX_SIZE = 1;
/** {@link #getBranchNodeProperty(StringBuilder, long)} or {@link #getEntryNodeProperty(StringBuilder, long)}. */
static final int IDX_CREATED = 0;
/** {@link #getBranchNodeProperty(StringBuilder, long)} or {@link #getEntryNodeProperty(StringBuilder, long)}. */
static final int IDX_MODIFIED = 1;
/** Default complete date format for Home Service display. */
private static final DateFormat COMP_DISP_FMT = new SimpleDateFormat( // NOPMD by r39 on 3/30/11 2:36 PM
"EEEE, dd MMM, yyyy, hh:mm:ss a", Locale.getDefault());
/** RTFC. */
private static final String FMT_DATE_ONLY = "yyyy-MM-dd";
/** RTFC. */
private static final String FMT_TIME_ONLY = "HH:mm:ss:SSS";
/** Default complete date format for Home Service to parse from view. */
private static final DateFormat COMP_PARSE_FMT = new SimpleDateFormat(FMT_DATE_ONLY + " " + FMT_TIME_ONLY, Locale // NOPMD by r39 on 4/4/11 5:32 PM
.getDefault());
/** Synchronization lock object. */
private static Object lock = new Object();
@Override
public String getEntryTreePath(final Map<String, Object> session, final Entry entry)
{
if (entry == null) {
throw JournalException.wrapperException(new IllegalArgumentException(Error.IAE_NULL));
}
final StringBuilder strBuilder = new StringBuilder();
strBuilder.append(Constant.ENTRYID_PREFIX + entry.getNodeId());
strBuilder.insert(0, getBranchPath(session, entry.getBranch()));
return strBuilder.toString();
}
/**
* RTFC. TODO: Unit Test.
*
* @param session - server side session.
* @param branch branch to which to extract the path.
* @return Branch path.
*/
String getBranchPath(final Map<String, Object> session, final Branch branch)
{
final StringBuilder strBuilder = new StringBuilder();
Branch parent = branch;
strBuilder.insert(0, Constant.BRANCHID_PREFIX + parent.getBranchId());
if (!Constant.ROOT_NAME.equals(parent.getName())) {
do {
parent = parent.getParent();
strBuilder.insert(0, Constant.BRANCHID_PREFIX + parent.getBranchId());
} while (!Constant.ROOT_NAME.equals(parent.getName()));
}
return strBuilder.toString();
}
@Override
public long addBranch(final Map<String, Object> session, final long parentId, final String branchName)
{
final Branch parentBranch = getDaoFacade().getBranchDAO().read(parentId);
if (parentBranch == null) {
throw new JournalException(Messages.Error.JE_INV_BRANCHID);
} else if (StringUtils.getInstance().isNullOrEmpty(branchName)) {
throw new JournalException(Messages.Error.JE_INV_BRANCHNAME);
}
final Branch branch = new Branch(branchName, parentBranch);
branch.setCreator((User) session.get(Constant.CURRENT_USER));
getDaoFacade().getBranchDAO().save(branch);
return branch.getBranchId();
}
@Override
public void deleteBranch(final Map<String, Object> session, final long branchId)
{
if (branchId == Constant.ROOT_ID) {
throw new JournalException(Messages.Error.JE_INV_BRANCHID);
}
final Branch branch = getDaoFacade().getBranchDAO().read(branchId);
if (branch == null) {
throw new JournalException(Messages.Error.JE_INV_BRANCHID);
}
final List<Branch> withSubBranches = new ArrayList<Branch>();
withSubBranches.add(branch);
final List<Branch> children = getChildren(branch);
if (children != null && !children.isEmpty()) {
withSubBranches.addAll(children);
}
for (final Branch next : withSubBranches) {
final List<Entry> entries = getDaoFacade().getEntryDAO().readAllByBranch(next.getBranchId());
if (entries != null) {
getDaoFacade().getEntryDAO().deleteAll(entries);
}
}
getDaoFacade().getBranchDAO().deleteAll(withSubBranches);
}
/**
* Factored out method to get Children of Branch.
*
* @param parent Branch to get Children from.
* @return Children of given Branch.
*/
private List<Branch> getChildren(final Branch parent)
{
final List<Branch> retval = getDaoFacade().getBranchDAO().readAllByParent(parent.getBranchId());
Collections.sort(retval, new Comparator<Branch>() {
@Override
public int compare(final Branch branch1, final Branch branch2)
{
return branch1.getName().compareToIgnoreCase(branch2.getName());
}
});
return retval;
}
@Override
public void renameBranch(final Map<String, Object> session, final long branchId, final String newName)
{
final Branch branch = getDaoFacade().getBranchDAO().read(branchId);
if (branch == null) {
throw new JournalException(Messages.Error.JE_INV_BRANCHID);
}
branch.setName(newName);
getDaoFacade().getBranchDAO().save(branch);
}
@Override
public List<Branch> getChildren(final Map<String, Object> session, final long parentId)
{
return getDaoFacade().getBranchDAO().readAllByParent(parentId);
}
@Override
public void moveBranch(final Map<String, Object> session, final long newParentId, final long branchId)
{
final BranchDAO branchDao = getDaoFacade().getBranchDAO();
final Branch branch = branchDao.read(branchId);
final Branch parent = branchDao.read(newParentId);
if (branch == null || parent == null) {
throw new JournalException(Messages.Error.JE_INV_BRANCHID);
}
final String newIds = Constant.BRANCHID_PREFIX + newParentId;
final String branchIds = Constant.BRANCHID_PREFIX + branchId;
final String branchPath = getBranchPath(session, parent);
final int newIdx = branchPath.indexOf(newIds);
final int parentIdx = branchPath.indexOf(branchIds);
if (parentIdx > -1 && newIdx > parentIdx) {
throw new JournalException(Messages.Error.JE_INV_BRANCHID);
}
branch.setParent(parent);
branchDao.save(branch);
}
@Override
public Map<String, Object> getNodeProperties(final Map<String, Object> session, final long nodeId,
final boolean isEntry)
{
final Map<String, Object> retval = new HashMap<String, Object>();
final StringBuilder path = new StringBuilder();
final Object[] beanData = isEntry ? getEntryNodeProperty(path, nodeId) : getBranchNodeProperty(path, nodeId);
final long beanSize = (Long) beanData[IDX_SIZE];
final Date[] actionDates = getActionDates((AbstractAuditableBean) beanData[IDX_BEAN]);
String parentName;
Object[] parentBeanData = new Object[beanData.length];
System.arraycopy(beanData, 0, parentBeanData, 0, beanData.length);
Branch parent = null;
do {
parent = (Branch) BeanUtils.getProperty(parentBeanData[IDX_BEAN],
parentBeanData[IDX_BEAN] instanceof Entry ? Entry.PARENT : Branch.ATTR_PARENT);
parentName = BeanUtils.getProperty(parent, Branch.ATTR_NAME).toString();
path.insert(0, "/" + parentName);
parentBeanData = getBranchNodeProperty(null, parent.getBranchId());
} while (!parentName.equals(Constant.ROOT_NAME));
synchronized (lock) {
retval.put(Constant.NodeProperty.CREATED, COMP_DISP_FMT.format(actionDates[IDX_CREATED]));
retval.put(Constant.NodeProperty.MODIFIED, COMP_DISP_FMT.format(actionDates[IDX_MODIFIED]));
}
final NumberFormat numFormat = NumberFormat.getIntegerInstance();
numFormat.setGroupingUsed(true);
retval.put(Constant.NodeProperty.SIZE, numFormat.format(beanSize));
retval.put(Constant.NodeProperty.PATH, path.toString());
return retval;
}
/**
* Generate size, create and update date, path (/<title>). Update date when null will default to create date. <br/>
* StringBuilder when null will simply ignore append of /<name>.
*
* Refactored out from {@link #getNodeProperties(Map, long, boolean)}.
*
* @see related {@link #getBranchNodeProperty(StringBuilder, long)}
*
* @param strBuilder String path to append Entry name.
* @param nodeId ID of Entry.
* @return {Entity, size}
* @exception JournalException when the nodeId passed cannot be found.
*/
Object[] getEntryNodeProperty(final StringBuilder strBuilder, final long nodeId)
{
final EntryDAO entryDao = getDaoFacade().getEntryDAO();
final Entry entry = entryDao.read(nodeId);
if (entry == null) {
throw JournalException.wrapperException(new IllegalArgumentException(Messages.Error.IAE_NULL));
}
long size = entry.getTitle().length();
if (entry.getDescription() != null) {
size += entry.getDescription().length();
}
if (strBuilder != null) {
strBuilder.append("/" + entry.getTitle());
}
return new Object[] {
entry,
size };
}
/**
* Refactored out from {@link #getNodeProperties(Map, long, boolean)}. StringBuilder when null will simply ignore
* append of /<name>.
*
* @see related {@link #getEntryNodeProperty(StringBuilder, long)}
*
* @param strBuilder String path to append Branch name.
* @param branchId ID of Branch.
* @return {Entity, size}
* @exception JournalException branchId is not found.
*/
Object[] getBranchNodeProperty(final StringBuilder strBuilder, final long branchId)
{
final BranchDAO branchDao = getDaoFacade().getBranchDAO();
final Branch branch = branchDao.read(branchId);
if (branch == null) {
throw JournalException.wrapperException(new IllegalArgumentException(Messages.Error.IAE_NULL));
}
final long size = branch.getName() == null ? 0 : branch.getName().length();
if (strBuilder != null) {
strBuilder.append("/" + branch.getName());
}
return new Object[] {
branch,
size };
}
/**
* TODO: Unit Testing. <br/>
* Refactored code. Will return Create date on index 0, Update date on index 1.
*
* @param bean either Entry or Branch.
* @return the action date array.
*/
Date[] getActionDates(final AbstractAuditableBean bean)
{
final Date[] retval = new Date[2];
final DateFormat dateFormat = new SimpleDateFormat(FMT_DATE_ONLY, Locale.getDefault());
final DateFormat timeFormat = new SimpleDateFormat(FMT_TIME_ONLY, Locale.getDefault());
if (bean.getCreateDate() != null && bean.getCreateTime() != null) {
try {
synchronized (lock) {
retval[IDX_CREATED] = COMP_PARSE_FMT.parse(dateFormat.format(bean.getCreateDate()) + " "
+ timeFormat.format(bean.getCreateTime()));
}
} catch (final ParseException ignore) {
getLogger().warn(ignore.getMessage(), ignore);
}
}
if (bean.getUpdateDate() != null && bean.getUpdateTime() != null) {
try {
synchronized (lock) {
retval[IDX_MODIFIED] = COMP_PARSE_FMT.parse(dateFormat.format(bean.getUpdateDate()) + " "
+ timeFormat.format(bean.getUpdateTime()));
}
} catch (final ParseException ignore) {
getLogger().warn(ignore.getMessage(), ignore);
}
}
return retval;
}
@Override
public List<Map<String, Object>> getPreferences(final Map<String, Object> session)
{
final List<Map<String, Object>> retval = new ArrayList<Map<String, Object>>();
final List<ConfigItem> allConfigItem = getDaoFacade().getConfigItemDAO().readAll();
final Map<Long, List<ConfigItem>> parentChildMap = new HashMap<Long, List<ConfigItem>>();
List<ConfigItem> children;
final List<ConfigItem> parentList = new ArrayList<ConfigItem>();
for (final ConfigItem configItem : allConfigItem) {
if (configItem.getParent() == null) {
parentList.add(configItem);
} else {
final long parentConfigId = configItem.getParent().getConfigId();
if (parentChildMap.get(parentConfigId) == null) {
parentChildMap.put(parentConfigId, new ArrayList<ConfigItem>()); // NOPMD by r39
}
children = parentChildMap.get(parentConfigId);
children.add(configItem);
}
}
for (final ConfigItem parent : parentList) {
retval.add(convertToMap(parent, parentChildMap));
}
return retval;
}
/**
* Recursive helper method for converting ConfigItem to Map.
*
* @param configItem ConfigItem object to converted.
* @param parentChildMap Parent to Children mapping.
* @return Converted ConfigItem
* @exception JournalException when any of the 2 parameter is null.
*/
Map<String, Object> convertToMap(final ConfigItem configItem,
final Map<Long, List<ConfigItem>> parentChildMap)
{
if (configItem == null || parentChildMap == null) {
throw JournalException.wrapperException(new IllegalArgumentException(Messages.Error.IAE_NULL));
}
final Map<String, Object> retval = BeanUtils.convertToMap(configItem, new String[] {
AbstractAuditableBean.CREATOR,
AbstractAuditableBean.UPDATER });
final List<ConfigItem> children = parentChildMap.get(configItem.getConfigId());
List<Map<String, Object>> childrenMap = null;
if (children != null) {
childrenMap = new ArrayList<Map<String, Object>>();
for (final ConfigItem child : children) {
childrenMap.add(convertToMap(child, parentChildMap));
}
retval.put(CHILDREN, childrenMap);
}
return retval;
}
}