package marubinotto.piggydb.impl;
import static marubinotto.util.CollectionUtils.joinToString;
import static marubinotto.util.CollectionUtils.list;
import static marubinotto.util.CollectionUtils.set;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import marubinotto.piggydb.impl.mapper.FragmentRelationRowMapper;
import marubinotto.piggydb.impl.mapper.FragmentRowMapper;
import marubinotto.piggydb.impl.query.H2FragmentsAllButTrash;
import marubinotto.piggydb.impl.query.H2FragmentsByFilter;
import marubinotto.piggydb.impl.query.H2FragmentsByIds;
import marubinotto.piggydb.impl.query.H2FragmentsByKeywords;
import marubinotto.piggydb.impl.query.H2FragmentsByTime;
import marubinotto.piggydb.impl.query.H2FragmentsByUser;
import marubinotto.piggydb.impl.query.H2FragmentsOfUser;
import marubinotto.piggydb.model.Fragment;
import marubinotto.piggydb.model.FragmentList;
import marubinotto.piggydb.model.FragmentRelation;
import marubinotto.piggydb.model.FragmentRepository;
import marubinotto.piggydb.model.Tag;
import marubinotto.piggydb.model.auth.User;
import marubinotto.piggydb.model.entity.RawClassifiable;
import marubinotto.piggydb.model.entity.RawEntityFactory;
import marubinotto.piggydb.model.entity.RawFilter;
import marubinotto.piggydb.model.entity.RawFragment;
import marubinotto.piggydb.model.enums.FragmentField;
import marubinotto.piggydb.model.exception.DuplicateException;
import marubinotto.piggydb.model.exception.NoSuchEntityException;
import marubinotto.util.Assert;
import marubinotto.util.time.Month;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer;
public class H2FragmentRepository extends FragmentRepository.Base
implements RawEntityFactory<RawFragment> {
private static Log logger = LogFactory.getLog(H2FragmentRepository.class);
private H2TagRepository tagRepository;
protected JdbcTemplate jdbcTemplate;
private DataFieldMaxValueIncrementer fragmentIdIncrementer;
private DataFieldMaxValueIncrementer relationIdIncrementer;
private FragmentRowMapper fragmentRowMapper = new FragmentRowMapper(this, "fragment.");
public H2FragmentRepository() {
registerQuery(H2FragmentsByTime.class);
registerQuery(H2FragmentsByUser.class);
registerQuery(H2FragmentsByKeywords.class);
registerQuery(H2FragmentsByIds.class);
registerQuery(H2FragmentsOfUser.class);
registerQuery(H2FragmentsByFilter.class);
registerQuery(H2FragmentsAllButTrash.class);
}
public RawEntityFactory<FragmentRelation> relationFactory =
new RawEntityFactory<FragmentRelation>() {
public FragmentRelation newRawEntity() {
return new FragmentRelation();
}
};
public void setTagRepository(H2TagRepository tagRepository) {
this.tagRepository = tagRepository;
}
public H2TagRepository getTagRepository() {
return this.tagRepository;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return this.jdbcTemplate;
}
public void setFragmentIdIncrementer(
DataFieldMaxValueIncrementer fragmentIdIncrementer) {
this.fragmentIdIncrementer = fragmentIdIncrementer;
}
public void setRelationIdIncrementer(
DataFieldMaxValueIncrementer relationIdIncrementer) {
this.relationIdIncrementer = relationIdIncrementer;
}
public FragmentRowMapper getFragmentRowMapper() {
return this.fragmentRowMapper;
}
public boolean containsId(Long id) throws Exception {
return this.jdbcTemplate.queryForInt(
"select count(*) from fragment where fragment_id = ?",
new Object[]{id}) > 0;
}
public long register(Fragment fragment) throws Exception {
Assert.Arg.notNull(fragment, "fragment");
Assert.require(fragment instanceof RawFragment, "fragment instanceof RawFragment");
Assert.Property.requireNotNull(fragmentIdIncrementer, "fragmentIdIncrementer");
Assert.Property.requireNotNull(fileRepository, "fileRepository");
if (fragment.getId() == null)
((RawFragment)fragment).setId(this.fragmentIdIncrementer.nextLongValue());
// Save the tag side
saveTagSide((RawFragment)fragment);
// Insert the fragment
FragmentRowMapper.insert((RawFragment)fragment, this.jdbcTemplate);
QueryUtils.registerTaggings(
fragment,
QueryUtils.TAGGING_TARGET_FRAGMENT,
this.jdbcTemplate,
this.tagRepository);
// To avoid registering only a record (transaction should be rolled back when IO error)
// To avoid registering only a file (should register a file after the db query succeeds)
if (fragment.isFile()) {
this.fileRepository.putFile(fragment);
}
return fragment.getId();
}
public long size() throws Exception {
return (Long)this.jdbcTemplate.queryForObject(
"select count(*) from fragment", Long.class);
}
public Fragment get(long id, boolean fetchRelations) throws Exception {
logger.debug("get: " + id);
// entity
RawFragment fragment = queryForOneFragment(
"select " + this.fragmentRowMapper.selectAll() + " from fragment where fragment_id = ?",
new Object[]{new Long(id)});
if (fragment == null) return null;
// classification
refreshClassifications(list(fragment));
// relationships
if (fetchRelations) {
setParentsTo(fragment);
FragmentList<RawFragment> fragment2 = new FragmentList<RawFragment>(fragment);
setChildrenToEach(fragment2);
FragmentList<RawFragment> children = fragment2.getChildren();
refreshClassifications(children.getFragments());
setParentsAndChildrenWithGrandchildrenToEach(children.getFragments());
fragment.checkTwoWayRelations();
}
return fragment;
}
private RawFragment queryForOneFragment(String sql, Object[] args) {
try {
return (RawFragment) this.jdbcTemplate.queryForObject(
sql, args, this.fragmentRowMapper);
}
catch (EmptyResultDataAccessException e) {
return null;
}
}
public List<Fragment> getFragmentsAtHome(User user) throws Exception {
Assert.Arg.notNull(user, "user");
RawFragment home = (RawFragment)getHome(true, user);
if (home == null) return new ArrayList<Fragment>();
FragmentList<RawFragment> children = home.getRawChildren();
fetchRelations(children.getFragments(), true, true);
return children.covariantCast();
}
public void updateFragment(Fragment fragment, boolean updateTimestamp)
throws Exception {
FragmentRowMapper.update((RawFragment)fragment, updateTimestamp, this.jdbcTemplate);
QueryUtils.updateTaggings(
(RawFragment)fragment,
QueryUtils.TAGGING_TARGET_FRAGMENT,
this.jdbcTemplate,
this.tagRepository);
// To avoid registering only a record (transaction should be rolled back when IO error)
// To avoid registering only a file (should register a file after the db query succeeds)
if (fragment.isFile() && fragment.getFileInput() != null) {
this.fileRepository.putFile(fragment);
}
}
@Override
protected void doDelete(Fragment fragment, User user) throws Exception {
// Delete the tag role
if (fragment.getTagId() != null) {
getTagRepository().delete(fragment.getTagId(), user);
}
// Delete the related taggings
this.jdbcTemplate.update(
"delete from tagging where target_id = ? and target_type = ?",
new Object[]{
fragment.getId(),
QueryUtils.TAGGING_TARGET_FRAGMENT
});
// Delete the relations
this.jdbcTemplate.update(
"delete from fragment_relation where from_id = ? or to_id = ?",
new Object[]{
fragment.getId(),
fragment.getId()
});
// Delete the fragment
this.jdbcTemplate.update(
"delete from fragment where fragment_id = ?",
new Object[]{fragment.getId()});
// To avoid deleting only a record (transaction should be rolled back when IO error)
// To avoid deleting only a file (should delete a file after the db query succeeds)
if (fragment.isFile()) {
this.fileRepository.deleteFile(fragment);
}
}
public void refreshClassifications(List<? extends Fragment> fragments) throws Exception {
Assert.Arg.notNull(fragments, "fragments");
if (fragments.isEmpty()) return;
// list -> map & duplications, clear all classifications
Map<Long, RawClassifiable> id2fragment = new HashMap<Long, RawClassifiable>();
List<RawFragment> duplications = new ArrayList<RawFragment>();
for (Fragment fragment : fragments) {
RawFragment fragmentImpl = (RawFragment)fragment;
fragmentImpl.getMutableClassification().clear();
if (id2fragment.containsKey(fragment.getId()))
duplications.add(fragmentImpl);
else
id2fragment.put(fragment.getId(), fragmentImpl);
}
// get classifications
QueryUtils.setTagsRecursively(
id2fragment,
QueryUtils.TAGGING_TARGET_FRAGMENT,
this.jdbcTemplate,
getTagRepository());
logger.debug("Classifications refreshed: " + id2fragment.keySet());
for (RawFragment duplication : duplications) {
duplication.getMutableClassification().syncWith(
id2fragment.get(duplication.getId()).getClassification());
}
}
public void deleteTrashes(User user) throws Exception {
for (Long trashId : selectIdsClassifiedAsTrash()) delete(trashId, user);
}
@SuppressWarnings("unchecked")
public Set<Integer> getDaysOfMonth(FragmentField field, Month month)
throws Exception {
Assert.Arg.notNull(field, "field");
Assert.Arg.notNull(month, "month");
StringBuilder sql = new StringBuilder();
sql.append("select distinct extract(DAY from " + field.getName() + ")");
sql.append(" from fragment");
sql.append(" where extract(YEAR from " + field.getName() + ") = ?");
sql.append(" and extract(MONTH from " + field.getName() + ") = ?");
appendConditionToExcludeSpecialFragments(sql);
appendConditionToExcludeTrash(sql, "fragment.fragment_id");
return new HashSet<Integer>(
this.jdbcTemplate.queryForList(sql.toString(), new Object[]{
new Integer(month.getYear()), new Integer(month.getMonth())},
Integer.class));
}
@SuppressWarnings("unchecked")
public List<RawFragment> query(String sql, Object[] args) throws Exception {
return this.jdbcTemplate.query(sql.toString(), args, this.fragmentRowMapper);
}
public Map<Long, String> getNames(Set<Long> ids) throws Exception {
Assert.Arg.notNull(ids, "ids");
return QueryUtils.getValuesForIds("fragment", "title", ids, this.jdbcTemplate);
}
@Override
protected long doCreateRelation(long from, long to, User user)
throws NoSuchEntityException, DuplicateException, Exception {
Assert.Property.requireNotNull(relationIdIncrementer, "relationIdIncrementer");
if (!containsId(from)) {
throw new NoSuchEntityException(from);
}
if (!containsId(to)) {
throw new NoSuchEntityException(to);
}
FragmentRelation newRelation = new FragmentRelation(user);
newRelation.setId(this.relationIdIncrementer.nextLongValue());
FragmentRelationRowMapper.insert(newRelation, from, to, this.jdbcTemplate);
return newRelation.getId();
}
public FragmentRelation getRelation(long relationId) throws Exception {
try {
return (FragmentRelation) this.jdbcTemplate.queryForObject(
"select * from fragment_relation where fragment_relation_id = ?",
new Object[] { new Long(relationId) },
new FragmentRelationRowMapper(this.relationFactory, this));
}
catch (EmptyResultDataAccessException e) {
return null;
}
}
@Override
protected void doDeleteRelation(long relationId) throws Exception {
this.jdbcTemplate.update(
"delete from fragment_relation where fragment_relation_id = ?",
new Object[]{ new Long(relationId) });
}
public Long countRelations() throws Exception {
return (Long)this.jdbcTemplate.queryForObject(
"select count(*) from fragment_relation", Long.class);
}
@Override
protected void doUpdateChildRelationPriorities(List<Long> relationOrder) throws Exception {
int count = relationOrder.size();
for (Long relationId : relationOrder) {
this.jdbcTemplate.update(
"update fragment_relation set priority = " + (count--) + " where fragment_relation_id = ?",
new Object[]{ relationId });
}
}
// Utilities
private List<Long> selectIdsClassifiedAsTrash() throws Exception {
Tag trashTag = this.tagRepository.getTrashTag();
if (trashTag == null) return new ArrayList<Long>();
RawFilter filter = new RawFilter();
filter.getIncludes().addTag(trashTag);
H2FragmentsByFilter query = (H2FragmentsByFilter)getQuery(H2FragmentsByFilter.class);
query.setFilter(filter);
return query.getFilteredIds(false);
}
public void appendConditionToExcludeTrash(StringBuilder sql, String columnNameForId)
throws Exception {
List<Long> trashIds = selectIdsClassifiedAsTrash();
if (trashIds.isEmpty()) return;
sql.append(" and ");
sql.append(columnNameForId);
sql.append(" not in (");
boolean first = true;
for (Long fragmentId : trashIds) {
if (first) first = false; else sql.append(", ");
sql.append(fragmentId);
}
sql.append(")");
}
public void appendConditionToExcludeSpecialFragments(StringBuilder sql) {
sql.append(" and fragment.fragment_id > 0");
}
// Resolve relations
public void fetchRelations(List<RawFragment> fragments, boolean level1, boolean level2)
throws Exception {
if (fragments.isEmpty()) return;
if (level1) {
refreshClassifications(fragments);
setParentsAndChildrenWithGrandchildrenToEach(fragments);
if (level2) {
FragmentList<RawFragment> children =
new FragmentList<RawFragment>(fragments).getChildren();
refreshClassifications(children.getFragments());
setParentsToEach(children);
for (RawFragment child : children) child.checkTwoWayRelations();
}
}
}
private void setParentsTo(RawFragment fragment) throws Exception {
List<FragmentRelation> parents =
getParentsForEach(set(fragment.getId())).get(fragment.getId());
if (parents == null) return;
fragment.setParentRelations(parents);
}
public void setParentsAndChildrenWithGrandchildrenToEach(List<RawFragment> fragments)
throws Exception {
if (fragments.isEmpty()) return;
FragmentList<RawFragment> fragments2 = new FragmentList<RawFragment>(fragments);
setParentsToEach(fragments2);
setChildrenToEach(fragments2);
setChildrenToEach(fragments2.getChildren());
for (RawFragment fragment : fragments2)
fragment.checkTwoWayRelations();
}
public void setParentsToEach(FragmentList<RawFragment> fragments) throws Exception {
Assert.Arg.notNull(fragments, "fragments");
if (fragments.isEmpty()) return;
// get & set
Map<Long, List<FragmentRelation>> id2parents = getParentsForEach(fragments.ids());
for (Long id : id2parents.keySet()) {
fragments.get(id).setParentRelations(id2parents.get(id));
}
// set to the duplicates
for (RawFragment duplication : fragments.getDuplicates()) {
duplication.setParentRelations(
fragments.get(duplication.getId()).getParentRelations());
}
}
public void setChildrenToEach(FragmentList<RawFragment> fragments) throws Exception {
Assert.Arg.notNull(fragments, "fragments");
if (fragments.isEmpty()) return;
// get & set (without sorting)
Map<Long, List<FragmentRelation>> id2children = getChildrenForEach(fragments.ids());
for (Long id : id2children.keySet()) {
fragments.get(id).setChildRelations(id2children.get(id));
}
// set to the duplicates
for (RawFragment duplication : fragments.getDuplicates()) {
duplication.setChildRelations(
fragments.get(duplication.getId()).getChildRelations());
}
}
private Map<Long, List<FragmentRelation>> getParentsForEach(Set<Long> fragmentIds)
throws NoSuchEntityException, Exception {
Map<Long, List<FragmentRelation>> results =
new HashMap<Long, List<FragmentRelation>>();
if (fragmentIds.isEmpty()) return results;
FragmentRelationRowMapper parentMapper =
new FragmentRelationRowMapper(
this.relationFactory,
"fragment_relation.",
this.fragmentRowMapper,
null,
results);
StringBuilder sql = new StringBuilder();
sql.append("select ");
sql.append(parentMapper.selectAll());
sql.append(", " + this.fragmentRowMapper.selectAll());
sql.append(" from fragment_relation, fragment");
sql.append(" where fragment_relation.from_id = fragment.fragment_id");
sql.append(" and fragment_relation.to_id in (");
sql.append(joinToString(fragmentIds, ", "));
sql.append(")");
appendConditionToExcludeTrash(sql, "fragment_relation.from_id");
if (logger.isDebugEnabled()) logger.debug("getParentsForEach: " + fragmentIds);
this.jdbcTemplate.query(sql.toString(), parentMapper);
return results;
}
private Map<Long, List<FragmentRelation>> getChildrenForEach(Set<Long> fragmentIds)
throws Exception {
Map<Long, List<FragmentRelation>> results =
new HashMap<Long, List<FragmentRelation>>();
if (fragmentIds.isEmpty()) return results;
FragmentRelationRowMapper childMapper =
new FragmentRelationRowMapper(
this.relationFactory,
"fragment_relation.",
null,
this.fragmentRowMapper,
results);
StringBuilder sql = new StringBuilder();
sql.append("select ");
sql.append(childMapper.selectAll());
sql.append(", " + this.fragmentRowMapper.selectAll());
sql.append(" from fragment_relation, fragment");
sql.append(" where fragment_relation.to_id = fragment.fragment_id");
sql.append(" and fragment_relation.from_id in (");
sql.append(joinToString(fragmentIds, ", "));
sql.append(")");
appendConditionToExcludeTrash(sql, "fragment_relation.to_id");
sql.append(" order by fragment_relation.priority desc nulls last");
sql.append(", fragment_relation.fragment_relation_id");
if (logger.isDebugEnabled()) logger.debug("getChildrenForEach: " + fragmentIds);
this.jdbcTemplate.query(sql.toString(), childMapper);
return results;
}
}