nl.strohalm.cyclos.dao.ads.AdDAOImpl.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.dao.ads.AdDAOImpl.java

Source

/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
    
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
    
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.dao.ads;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import nl.strohalm.cyclos.dao.IndexedDAOImpl;
import nl.strohalm.cyclos.entities.ads.Ad;
import nl.strohalm.cyclos.entities.ads.Ad.Status;
import nl.strohalm.cyclos.entities.ads.AdCategory;
import nl.strohalm.cyclos.entities.ads.AdCategoryWithCounterQuery;
import nl.strohalm.cyclos.entities.ads.AdCategoryWithCounterVO;
import nl.strohalm.cyclos.entities.ads.AdQuery;
import nl.strohalm.cyclos.entities.ads.FullTextAdQuery;
import nl.strohalm.cyclos.entities.customization.fields.AdCustomFieldValue;
import nl.strohalm.cyclos.entities.customization.fields.MemberCustomFieldValue;
import nl.strohalm.cyclos.entities.customization.images.AdImage;
import nl.strohalm.cyclos.entities.exceptions.DaoException;
import nl.strohalm.cyclos.entities.exceptions.QueryParseException;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
import nl.strohalm.cyclos.utils.DateHelper;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.Period;
import nl.strohalm.cyclos.utils.TimePeriod;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.hibernate.HibernateCustomFieldHandler;
import nl.strohalm.cyclos.utils.hibernate.HibernateHelper;
import nl.strohalm.cyclos.utils.lucene.Filters;
import nl.strohalm.cyclos.utils.lucene.LuceneUtils;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Filter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TotalHitCountCollector;

/**
 * Implementation class for ad DAO
 * @author rafael
 */
public class AdDAOImpl extends IndexedDAOImpl<Ad> implements AdDAO {

    private static class FullTextSearchElements {
        private final Query query;
        private final Filters filters;
        private final Sort sort;

        public FullTextSearchElements(final Query query, final Filters filters, final Sort sort) {
            this.query = query;
            this.filters = filters;
            this.sort = sort;
        }

        public Filters getFilters() {
            return filters;
        }

        public Query getQuery() {
            return query;
        }

        public Sort getSort() {
            return sort;
        }

    }

    private static final String[] FIELDS_FULL_TEXT = { "title", "description", "customValues", "owner.name",
            "owner.email", "owner.username", "owner.customValues" };
    private HibernateCustomFieldHandler hibernateCustomFieldHandler;

    public AdDAOImpl() {
        super(Ad.class);
    }

    @Override
    public int delete(final boolean flush, final Long... ids) {
        if (ids != null && ids.length > 0) {
            final Map<String, Object> namedParameters = new HashMap<String, Object>();
            namedParameters.put("ids", Arrays.asList(ids));
            bulkUpdate("delete from " + AdCustomFieldValue.class.getName() + " v where v.ad.id in (:ids)",
                    namedParameters);
            bulkUpdate("delete from " + AdImage.class.getName() + " ai where ai.ad.id in (:ids)", namedParameters);
            final Integer results = CoercionHelper.coerce(Integer.TYPE, bulkUpdate(
                    "update Ad ad set ad.deleteDate = current_date(), ad.description = null where ad.id in (:ids)",
                    namedParameters));
            if (flush) {
                flush();
            }
            return results;
        } else {
            return 0;
        }
    }

    @Override
    public List<Ad> fullTextSearch(final FullTextAdQuery adQuery) throws DaoException {
        FullTextSearchElements elements = prepare(adQuery);
        return list(Ad.class, adQuery, elements.getQuery(), elements.getFilters(), elements.getSort());
    }

    @Override
    public List<AdCategoryWithCounterVO> getCategoriesWithCounters(final List<AdCategory> categories,
            final AdCategoryWithCounterQuery acQuery) {
        IndexReader reader = null;
        IndexSearcher searcher = null;
        List<AdCategoryWithCounterVO> result = new ArrayList<AdCategoryWithCounterVO>(categories.size());
        try {
            reader = indexHandler.openReader(Ad.class);
            searcher = new IndexSearcher(reader);
            FullTextAdQuery adQuery = new FullTextAdQuery();
            adQuery.setStatus(Ad.Status.ACTIVE);
            adQuery.setTradeType(acQuery.getTradeType());
            if (acQuery.isExternalPublication()) {
                adQuery.setExternalPublication(true);
            }
            Long[] groupIds = acQuery.getGroupIds();
            if (groupIds != null) {
                adQuery.setGroups(
                        Arrays.asList(EntityHelper.references(MemberGroup.class, Arrays.asList(groupIds))));
            }
            FullTextSearchElements elements = prepare(adQuery);
            Query query = elements.getQuery();
            Filters baseFilters = elements.getFilters();
            for (AdCategory adCategory : categories) {
                AdCategoryWithCounterVO counter = createCounter(searcher, query, baseFilters, adCategory, null, 1);
                result.add(counter);
            }
            return result;
        } catch (Exception e) {
            throw new DaoException(e);
        } finally {
            try {
                searcher.close();
            } catch (final Exception e) {
                // Silently ignore
            }
            try {
                reader.close();
            } catch (final Exception e) {
                // Silently ignore
            }
        }
    }

    @Override
    public Integer getNumberOfAds(final Calendar date, final Collection<? extends Group> groups,
            final Ad.Status status) {
        return count(date, groups, status, "count(ad.id)");
    }

    @Override
    public Integer getNumberOfCreatedAds(final Period period, final Collection<? extends Group> groups) {
        final Map<String, Object> namedParameters = new HashMap<String, Object>();
        final StringBuilder hql = new StringBuilder("select count(ad.id) from Ad ad where 1=1");
        HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "ad.creationDate", period);
        if (!CollectionUtils.isEmpty(groups)) {
            hql.append(" and ad.owner.group in (:groups) ");
            namedParameters.put("groups", groups);
        }
        return uniqueResult(hql.toString(), namedParameters);
    }

    @Override
    public Integer getNumberOfMembersWithAds(final Calendar date, final Collection<? extends Group> groups)
            throws DaoException {
        return count(date, groups, Ad.Status.ACTIVE, "count(distinct ad.owner.id)");
    }

    @Override
    public List<Ad> search(final AdQuery query) {
        final Map<String, Object> namedParameters = new HashMap<String, Object>();
        final StringBuilder hql = new StringBuilder();
        hql.append(" select ad");
        hql.append(
                " from Ad ad inner join ad.owner m left join ad.category c1 left join c1.parent c2 left join c2.parent c3 ");
        hibernateCustomFieldHandler.appendJoins(hql, "ad.customValues", query.getAdValues());
        hibernateCustomFieldHandler.appendJoins(hql, "m.customValues", query.getMemberValues());
        HibernateHelper.appendJoinFetch(hql, getEntityType(), "ad", query.getFetch());
        hql.append(" where 1=1");
        if (query.getCategory() != null) {
            hql.append(" and (c1 = :adCategory or c2 = :adCategory or c3 = :adCategory)");
            namedParameters.put("adCategory", query.getCategory());
        }
        if (!query.isIncludeDeleted()) {
            hql.append(" and ad.deleteDate is null ");
        }

        HibernateHelper.addParameterToQuery(hql, namedParameters, "ad.category.active", true);
        HibernateHelper.addParameterToQuery(hql, namedParameters, "ad.id", query.getId());
        HibernateHelper.addParameterToQuery(hql, namedParameters, "ad.membersNotified", query.getMembersNotified());
        HibernateHelper.addParameterToQuery(hql, namedParameters, "ad.externalPublication",
                query.getExternalPublication());
        HibernateHelper.addParameterToQuery(hql, namedParameters, "m", query.getOwner());
        // Group filters are handled at service level
        HibernateHelper.addInParameterToQuery(hql, namedParameters, "m.group", query.getGroups());
        HibernateHelper.addParameterToQuery(hql, namedParameters, "ad.tradeType", query.getTradeType());
        HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.price", ">=",
                query.getInitialPrice());
        HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.price", "<=", query.getFinalPrice());
        HibernateHelper.addParameterToQuery(hql, namedParameters, "ad.currency", query.getCurrency());
        HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "ad.publicationPeriod.begin",
                Period.day(query.getBeginDate()));
        HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "ad.publicationPeriod.end",
                Period.day(query.getEndDate()));

        final Calendar now = Calendar.getInstance();
        // Since
        if (query.getSince() != null && query.getSince().getNumber() > 0) {
            final Calendar since = DateHelper.truncate(query.getSince().remove(now));
            HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.publicationPeriod.begin", ">=",
                    since);
        }
        HibernateHelper.addPeriodParameterToQuery(hql, namedParameters, "ad.publicationPeriod.begin",
                query.getPeriod());

        // With Images Only
        if (query.isWithImagesOnly()) {
            hql.append(" and exists (select img.id from AdImage img where img.ad = ad) ");
        }

        // Check the history date
        final Calendar historyDate = (Calendar) ObjectUtils.defaultIfNull(query.getHistoryDate(),
                Calendar.getInstance());
        HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.creationDate", "<=", historyDate);
        hql.append(" and (ad.deleteDate is null or ad.deleteDate >= :historyDate)");
        namedParameters.put("historyDate", historyDate);

        // Status
        if (query.getStatus() != null) {
            switch (query.getStatus()) {
            case PERMANENT:
                hql.append(" and ad.permanent = true ");
                break;
            case ACTIVE:
                hql.append(
                        " and (ad.permanent = true or ((ad.publicationPeriod.end is null or ad.publicationPeriod.end >= :historyDate) and ad.publicationPeriod.begin <= :historyDate)) ");
                break;
            case SCHEDULED:
                hql.append(
                        " and (ad.permanent is null or ad.permanent = false) and ad.publicationPeriod.begin > :historyDate ");
                break;
            case EXPIRED:
                hql.append(
                        " and (ad.permanent is null or ad.permanent = false) and ad.publicationPeriod.end < :historyDate ");
                break;
            }
        }

        // Keywords
        if (StringUtils.isNotEmpty(query.getKeywords())) {
            hql.append(" and ((ad.title like :keywords) or (ad.description like :keywords))");
            namedParameters.put("keywords", "%" + query.getKeywords() + "%");
        }
        // Custom Values
        hibernateCustomFieldHandler.appendConditions(hql, namedParameters, query.getAdValues());
        hibernateCustomFieldHandler.appendConditions(hql, namedParameters, query.getMemberValues());

        // Handle order
        if (query.isRandomOrder()) {
            HibernateHelper.appendOrder(hql, "rand()");
        } else {
            HibernateHelper.appendOrder(hql, "ad.publicationPeriod.begin desc, ad.id desc");
        }
        return list(query, hql.toString(), namedParameters);
    }

    public void setHibernateCustomFieldHandler(final HibernateCustomFieldHandler hibernateCustomFieldHandler) {
        this.hibernateCustomFieldHandler = hibernateCustomFieldHandler;
    }

    private Integer count(Calendar date, final Collection<? extends Group> groups, final Ad.Status status,
            final String projection) {
        if (date == null) {
            date = Calendar.getInstance();
        }
        final Map<String, Object> namedParameters = new HashMap<String, Object>();
        final StringBuilder hql = new StringBuilder("select " + projection + " from Ad ad where 1=1");
        HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.creationDate", "<=", date);
        hql.append(" and (ad.deleteDate is null or ad.deleteDate > :date)");
        if (!CollectionUtils.isEmpty(groups)) {
            hql.append(" and ad.owner.group in (:groups) ");
            namedParameters.put("groups", groups);
        }
        switch (status) {
        case ACTIVE:
            hql.append(" and ad.publicationPeriod.begin <= :date");
            hql.append(" and (ad.permanent = true or ad.publicationPeriod.end > :date)");
            break;
        case PERMANENT:
            hql.append(" and ad.publicationPeriod.begin <= :date");
            hql.append(" and ad.permanent = true");
            break;
        case EXPIRED:
            HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.publicationPeriod.begin", "<=",
                    date);
            HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.publicationPeriod.end", "<=",
                    date);
            break;
        case SCHEDULED:
            HibernateHelper.addParameterToQueryOperator(hql, namedParameters, "ad.publicationPeriod.begin", ">",
                    date);
            break;
        }
        namedParameters.put("date", date);
        return uniqueResult(hql.toString(), namedParameters);
    }

    private AdCategoryWithCounterVO createCounter(final IndexSearcher searcher, final Query query,
            final Filters baseFilters, final AdCategory adCategory, final AdCategoryWithCounterVO parent,
            final int level) throws IOException {
        // Run with filters based on the current category
        Filters filters = (Filters) baseFilters.clone();
        filters.addTerms("category", adCategory.getId());
        TotalHitCountCollector collector = new TotalHitCountCollector();
        searcher.search(query, filters, collector);
        int totalCount = collector.getTotalHits();
        AdCategoryWithCounterVO counter = new AdCategoryWithCounterVO(adCategory.getId(), adCategory.getName(),
                level, totalCount, parent);
        // Repeat recursively for each child
        for (AdCategory childCategory : adCategory.getChildren()) {
            AdCategoryWithCounterVO childCounter = createCounter(searcher, query, baseFilters, childCategory,
                    counter, level + 1);
            counter.addChild(childCounter);
        }
        return counter;
    }

    private MultiFieldQueryParser getQueryParser(final Analyzer analyzer) {
        final Map<String, Float> boosts = new HashMap<String, Float>();
        boosts.put("title", 2.5F);
        boosts.put("description", 2F);
        boosts.put("owner.name", 1.5F);
        boosts.put("owner.username", 1.3F);
        return new MultiFieldQueryParser(LuceneUtils.LUCENE_VERSION, FIELDS_FULL_TEXT, analyzer, boosts);
    }

    private FullTextSearchElements prepare(final FullTextAdQuery adQuery) {
        final String keywords = adQuery.getKeywords();
        final Calendar today = DateHelper.truncate(Calendar.getInstance());
        Sort sort = null;
        Query query;
        Analyzer analyzer = adQuery.getAnalyzer();
        if (keywords == null) {
            query = new MatchAllDocsQuery();
            // When not using keywords, return newer first
            sort = new Sort(new SortField("baseDate", SortField.STRING, true));
        } else {
            try {
                query = getQueryParser(analyzer).parse(keywords);
            } catch (final ParseException e) {
                throw new QueryParseException(e);
            }
        }

        final Filters filters = new Filters();
        filters.addTerms("id", adQuery.getId());
        filters.addTerms("membersNotified", adQuery.getMembersNotified());
        filters.addTerms("category", adQuery.getCategoriesIds());
        filters.addTerms("currency", adQuery.getCurrency());
        filters.addTerms("externalPublication", adQuery.getExternalPublication());
        filters.addRange("price", adQuery.getInitialPrice(), adQuery.getFinalPrice());
        final TimePeriod since = adQuery.getSince();
        if (since != null && since.isValid()) {
            final Calendar sinceDate = since.remove(today);
            filters.addRange("publicationBegin", sinceDate, null);
        }
        filters.addPeriod("publicationBegin", adQuery.getPeriod());
        filters.addTerms("owner", adQuery.getOwner());
        filters.addTerms("owner.group", adQuery.getGroups());
        filters.addTerms("tradeType", adQuery.getTradeType());
        if (CollectionUtils.isNotEmpty(adQuery.getAdValues())) {
            for (final AdCustomFieldValue fieldValue : adQuery.getAdValues()) {
                addCustomField(filters, analyzer, fieldValue);
            }
        }
        if (CollectionUtils.isNotEmpty(adQuery.getMemberValues())) {
            for (final MemberCustomFieldValue fieldValue : adQuery.getMemberValues()) {
                addCustomField(filters, analyzer, fieldValue, "owner.customValues.%s");
            }
        }
        if (adQuery.isWithImagesOnly()) {
            filters.addTerms("hasImages", true);
        }
        // Status
        final Status status = adQuery.getStatus();
        if (status != null) {
            final Filter isPermanent = Filters.terms("permanent", true);
            final Filter isNotPermanent = Filters.terms("permanent", false);
            Filter endRange;
            Filter beginRange;
            switch (status) {
            case PERMANENT:
                // permanent = true
                filters.add(isPermanent);
                break;
            case ACTIVE:
                // permanent = true or (end > today and begin <= today) // neither begin / end are null
                beginRange = Filters.range("publicationBegin", null, today);
                endRange = Filters.range("publicationEnd", today, null);
                filters.add(Filters.or(isPermanent, Filters.and(endRange, beginRange)));
                break;
            case SCHEDULED:
                // permanent = false and begin >= today
                beginRange = Filters.range("publicationBegin", today, null);
                filters.add(Filters.and(isNotPermanent, beginRange));
                break;
            case EXPIRED:
                // permanent = false and end <= today
                endRange = Filters.range("publicationEnd", null, today, false, false);
                filters.add(Filters.and(isNotPermanent, endRange));
                break;
            }
        }
        return new FullTextSearchElements(query, filters, sort);
    }
}