/* 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.services.ads; import java.util.ArrayList; import java.util.Collection; import java.util.List; import nl.strohalm.cyclos.dao.ads.AdCategoryDAO; import nl.strohalm.cyclos.entities.Relationship; import nl.strohalm.cyclos.entities.ads.AdCategory; import nl.strohalm.cyclos.entities.ads.AdCategoryQuery; import nl.strohalm.cyclos.entities.ads.AdQuery; import nl.strohalm.cyclos.entities.exceptions.DaoException; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.services.fetch.FetchServiceLocal; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.utils.EntityHelper; import nl.strohalm.cyclos.utils.RelationshipHelper; import nl.strohalm.cyclos.utils.XmlHelper; import nl.strohalm.cyclos.utils.cache.Cache; import nl.strohalm.cyclos.utils.cache.CacheCallback; import nl.strohalm.cyclos.utils.cache.CacheManager; import nl.strohalm.cyclos.utils.query.PageHelper; import nl.strohalm.cyclos.utils.validation.GeneralValidation; import nl.strohalm.cyclos.utils.validation.ValidationError; import nl.strohalm.cyclos.utils.validation.ValidationException; import nl.strohalm.cyclos.utils.validation.Validator; import nl.strohalm.cyclos.webservices.model.AdCategoryHierarchicalVO; import nl.strohalm.cyclos.webservices.utils.AdHelper; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.springframework.dao.DataIntegrityViolationException; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Implementation class for the Advertisement service interface * @author rafael * @author luis * @author Lucas Geiss */ public class AdCategoryServiceImpl implements AdCategoryServiceLocal { private final String ROOT_ELEMENT = "ad-categories"; private final String CATEGORY_ELEMENT = "ad-category"; private final String NAME_ATTRIBUTE = "name"; private final String LEAF_CACHE_KEY = "_LEAF_"; private final String ROOT_CACHE_KEY = "_ROOT_"; private AdServiceLocal adService; private SettingsServiceLocal settingsService; private AdCategoryDAO adCategoryDao; private FetchServiceLocal fetchService; private CacheManager cacheManager; private AdHelper adHelper; @Override public String exportToXml() { final LocalSettings localSettings = settingsService.getLocalSettings(); final StringBuilder xml = new StringBuilder(); xml.append("<?xml version=\"1.0\" encoding=\"").append(localSettings.getCharset()).append("\"?>\n"); xml.append('<').append(ROOT_ELEMENT).append(">\n"); final List<AdCategory> categories = listRoot(); for (final AdCategory adCategory : categories) { appendXml(xml, adCategory); } xml.append("</").append(ROOT_ELEMENT).append(">\n"); return xml.toString(); } @Override public List<Long> getActiveCategoriesId() { return adCategoryDao.getActiveCategoriesId(); } @Override public AdCategoryHierarchicalVO getHierarchicalVO(final AdCategory category) { return adHelper.toHierarchicalVO(category); } @Override public void importFromXml(final String xml) { final Document doc = XmlHelper.readDocument(xml); final Element root = doc.getDocumentElement(); // Find the order where the new root categories will start int rootOrder = 0; final List<AdCategory> rootCategories = listRoot(); for (final AdCategory adCategory : rootCategories) { final Integer order = adCategory.getOrder(); if (order != null && order > rootOrder) { rootOrder = order; } } // Insert the root categories final List<Element> childen = XmlHelper.getChilden(root, CATEGORY_ELEMENT); for (final Element elem : childen) { importCategory(null, ++rootOrder, elem); } invalidateCache(); } @Override public List<AdCategory> listLeaf() { return getCache().get(LEAF_CACHE_KEY, new CacheCallback() { @Override public Object retrieve() { AdCategoryQuery query = new AdCategoryQuery(); List<AdCategory> categories = new ArrayList<AdCategory>(); for (AdCategory category : adCategoryDao.searchLeafAdCategories(query)) { categories.add(fetch(category)); } return categories; } }); } @Override public List<AdCategory> listRoot() { return getCache().get(ROOT_CACHE_KEY, new CacheCallback() { @Override public Object retrieve() { AdCategoryQuery query = new AdCategoryQuery(); List<AdCategory> raw = adCategoryDao.search(query); List<AdCategory> list = new ArrayList<AdCategory>(raw.size()); for (AdCategory category : raw) { category = fetch(category); if (category != null) { list.add(category); } } return list; } }); } @Override public AdCategory load(final Long id, final Relationship... fetch) { return adCategoryDao.load(id, fetch); } @Override public int remove(final Long... ids) { final AdQuery adQuery = new AdQuery(); adQuery.setPageForCount(); for (final Long id : ids) { adQuery.setCategory(EntityHelper.reference(AdCategory.class, id)); if (PageHelper.getTotalCount(adService.search(adQuery)) > 0) { throw new DaoException(new DataIntegrityViolationException("category")); } } invalidateCache(); return adCategoryDao.delete(ids); } @Override public AdCategory save(final AdCategory category) { // Validates whether the ad category is valid or not validate(category); AdCategory current = null; if (category.isTransient()) { Integer order = category.getOrder(); if (order == null || order <= 0) { // Get the next order AdCategoryQuery query = new AdCategoryQuery(); query.setParent(category.getParent()); int maxOrder = 0; for (AdCategory cat : adCategoryDao.search(query)) { if (cat.getOrder() > maxOrder) { maxOrder = cat.getOrder(); } } category.setOrder(maxOrder + 1); } current = adCategoryDao.insert(category); if (category.getParent() != null) { category.getParent().getChildren().add(current); } } else { current = adCategoryDao.load(category.getId(), AdCategory.Relationships.CHILDREN); // Only the name and active status can be updated current.setName(category.getName()); // When the category is deactivated, we should also deactivate all children final boolean deactivated = (current.isActive() && !category.isActive()); if (deactivated) { // this method calls the adCategoryDao.update(current) so the above changes (like name) are also stored. deactivateRecursively(current); } else { final boolean changedActive = current.isActive() != category.isActive(); if (changedActive) { current.setActive(category.isActive()); } current = adCategoryDao.update(current); } } invalidateCache(); return current; } @Override public List<AdCategory> search(final AdCategoryQuery query) { return adCategoryDao.search(query); } @Override public List<AdCategory> searchLeafAdCategories(final AdCategoryQuery query) { return adCategoryDao.searchLeafAdCategories(query); } public void setAdCategoryDao(final AdCategoryDAO adCategoryDao) { this.adCategoryDao = adCategoryDao; } public void setAdHelper(final AdHelper adHelper) { this.adHelper = adHelper; } public void setAdServiceLocal(final AdServiceLocal adService) { this.adService = adService; } public void setCacheManager(final CacheManager cacheManager) { this.cacheManager = cacheManager; } public void setFetchServiceLocal(final FetchServiceLocal fetchService) { this.fetchService = fetchService; } /** * Set ad category order */ @Override public void setOrder(final Long[] ids) { int index = 0; for (final Long id : ids) { final AdCategory adCategory = load(id); adCategory.setOrder(++index); adCategoryDao.update(adCategory); } invalidateCache(); } public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) { this.settingsService = settingsService; } @Override public void validate(final AdCategory category) throws ValidationException { getValidator().validate(category); } private void appendXml(final StringBuilder xml, final AdCategory adCategory) { final String indent = StringUtils.repeat(" ", adCategory.getLevel()); xml.append(String.format("%s<%s %s=\"%s\"", indent, CATEGORY_ELEMENT, NAME_ATTRIBUTE, StringEscapeUtils.escapeXml(adCategory.getName()))); final Collection<AdCategory> children = adCategory.getChildren(); if (CollectionUtils.isEmpty(children)) { xml.append(" />\n"); } else { xml.append(">\n"); for (final AdCategory child : children) { appendXml(xml, child); } xml.append(indent).append("</").append(CATEGORY_ELEMENT).append(">\n"); } } private void deactivateRecursively(final AdCategory adCategory) { adCategory.setActive(false); adCategoryDao.update(adCategory); // Recursively deactivate children for (final AdCategory child : adCategory.getChildren()) { deactivateRecursively(child); } } private AdCategory fetch(AdCategory category) { category = fetchService.fetch(category, RelationshipHelper.nested(AdCategory.MAX_LEVEL, AdCategory.Relationships.PARENT), AdCategory.Relationships.CHILDREN); if (!category.isActive()) { return null; } List<AdCategory> children = new ArrayList<AdCategory>(); for (AdCategory child : category.getChildren()) { child = fetch(child); if (child != null) { children.add(child); } } category.setChildren(children); return category; } private Cache getCache() { return cacheManager.getCache("cyclos.AdCategories"); } private Validator getValidator() { final Validator validator = new Validator("adCategory"); validator.property("name").required().maxLength(100); validator.general(new GeneralValidation() { private static final long serialVersionUID = -8975710041548036332L; @Override public ValidationError validate(final Object object) { final AdCategory category = (AdCategory) object; if (category.isActive()) { // Ensure that an active category has no inactive parents AdCategory current = fetchService.fetch(category.getParent(), RelationshipHelper.nested(AdCategory.MAX_LEVEL, AdCategory.Relationships.PARENT)); while (current != null) { if (!current.isActive()) { return new ValidationError("adCategory.error.cantActivateCategoryWithInactiveParent"); } current = current.getParent(); } } return null; } }); return validator; } private AdCategory importCategory(final AdCategory parent, final int order, final Element elem) { Collection<AdCategory> toCheck = null; if (parent == null) { toCheck = listRoot(); } else { final AdCategory cat = load(parent.getId(), AdCategory.Relationships.CHILDREN); toCheck = cat.getChildren(); } AdCategory matchedChild = null; if (toCheck != null) { for (final AdCategory cat : toCheck) { if (cat.getName().equals(StringUtils.trimToNull(elem.getAttribute(NAME_ATTRIBUTE))) && cat.isActive()) { matchedChild = cat; } } } AdCategory category = null; if (matchedChild != null) { category = matchedChild; } else { category = new AdCategory(); category.setName(StringUtils.trimToNull(elem.getAttribute(NAME_ATTRIBUTE))); category.setParent(parent); category.setOrder(order); category.setActive(true); category = adCategoryDao.insert(category); } int childOrder = 0; final List<AdCategory> children = new ArrayList<AdCategory>(); final List<Element> childCategories = XmlHelper.getChilden(elem, CATEGORY_ELEMENT); for (final Element child : childCategories) { final AdCategory cat = importCategory(category, ++childOrder, child); if (cat != null) { children.add(cat); } } category.setChildren(children); return category; } private void invalidateCache() { getCache().clear(); adService.invalidateCountersCache(); } }