/*
* Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library 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 Lesser General Public License for more
* details.
*/
package com.agiletec.aps.system.services.category;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.ListableBeanFactory;
import com.agiletec.aps.system.common.AbstractService;
import com.agiletec.aps.system.common.tree.ITreeNode;
import com.agiletec.aps.system.exception.ApsSystemException;
import com.agiletec.aps.system.services.lang.ILangManager;
import com.agiletec.aps.system.services.lang.Lang;
import com.agiletec.aps.util.ApsProperties;
import com.agiletec.aps.util.DateConverter;
/**
* Manager delle Categorie.
* @author E.Santoboni
*/
public class CategoryManager extends AbstractService implements ICategoryManager {
private static final Logger _logger = LoggerFactory.getLogger(CategoryManager.class);
@Override
public void init() throws Exception {
this.loadCategories();
_logger.debug("{} ready. {} categories initialized", this.getClass().getName(),this._categories.size());
}
/**
* Caricamento della lista di categorie da db
* @throws ApsSystemException in caso di errore nell'accesso al db.
*/
protected void loadCategories() throws ApsSystemException {
List<Category> categories = null;
try {
categories = this.getCategoryDAO().loadCategories(this.getLangManager());
if (categories.isEmpty()) {
Category root = this.createRoot();
this.addCategory(root);
} else this.build(categories);
} catch (Throwable t) {
_logger.error("Error loading the category tree", t);
throw new ApsSystemException("Error loading the category tree.", t);
}
}
private void build(List<Category> categories) throws ApsSystemException {
try {
Category root = null;
Map<String, Category> categoryMap = new HashMap<String, Category>();
for (int i = 0; i < categories.size(); i++) {
Category cat = (Category) categories.get(i);
categoryMap.put(cat.getCode(), cat);
if (cat.getCode().equals(cat.getParentCode())) {
root = cat;
}
}
for (int i = 0; i < categories.size(); i++) {
Category cat = (Category) categories.get(i);
Category parent = (Category) categoryMap.get(cat.getParentCode());
if (cat != root) {
parent.addChild(cat);
}
cat.setParent(parent);
}
if (root == null) {
throw new ApsSystemException( "Error found in the category tree: undefined root");
}
_root = root;
_categories = categoryMap;
} catch (ApsSystemException e) {
throw e;
} catch (Throwable t) {
_logger.error("Error building the category tree", t);
throw new ApsSystemException("Error building the category tree", t);
}
}
/**
* Aggiunge una categoria al db.
* @param category La categoria da aggiungere.
* @throws ApsSystemException In caso di errore nell'accesso al db.
*/
@Override
public void addCategory(Category category) throws ApsSystemException {
try {
this.getCategoryDAO().addCategory(category);
} catch (Throwable t) {
_logger.error("Error detected while adding a category", t);
throw new ApsSystemException("Error detected while adding a category", t);
}
this.loadCategories();
}
/**
* Cancella una categoria.
* @param code Il codice della categoria da eliminare.
* @throws ApsSystemException in caso di errore nell'accesso al db.
*/
@Override
public void deleteCategory(String code) throws ApsSystemException {
Category cat = (Category) this.getCategories().get(code);
if (cat != null && cat.getChildren().length <= 0) {
try {
this.getCategoryDAO().deleteCategory(code);
} catch (Throwable t) {
_logger.error("Error detected while removing the category {}", code, t);
throw new ApsSystemException("Error detected while removing a category", t);
}
}
this.loadCategories();
}
/**
* Aggiorna una categoria nel db.
* @param category La categoria da modificare.
* @throws ApsSystemException In caso di errore nell'accesso al db.
*/
@Override
public void updateCategory(Category category) throws ApsSystemException {
try {
this.getCategoryDAO().updateCategory(category);
} catch (Throwable t) {
_logger.error("Error detected while updating a category", t);
throw new ApsSystemException("Error detected while updating a category", t);
}
this.loadCategories();
}
/**
* Restituisce la mappa delle categorie, indicizzate per codice.
* @return La mappa delle categorie.
*/
private Map<String, Category> getCategories() {
return this._categories;
}
/**
* Restituisce la radice dell'albero delle categorie
* @return la categoria radice
*/
@Override
public Category getRoot() {
return _root;
}
/**
* Metodo di utilità per l'inizializzazione del servizio.
* Il metodo viene invocato esclusivamente quando non esiste nessuna
* categoria nel db e vi è la necessità di creare la Categoria radice dell'albero.
* @return La categoria radice creata.
*/
protected Category createRoot() {
Category root = new Category();
root.setCode("home");
root.setParentCode("home");
List<Lang> langs = this.getLangManager().getLangs();
ApsProperties titles = new ApsProperties();
for (int i=0; i<langs.size(); i++) {
Lang lang = (Lang) langs.get(i);
titles.setProperty(lang.getCode(), "Home");
}
root.setTitles(titles);
return root;
}
/**
* Restituisce una lista piatta delle categorie disponibili,
* ordinate secondo la gerarchia dell'albero delle categorie.
* La categoria root non viene inclusa nella lista.
* @return La lista piatta delle categorie disponibili.
*/
@Override
public List<Category> getCategoriesList() {
List<Category> categories = new ArrayList<Category>();
if (null != this.getRoot()) {
for (int i=0; i<this.getRoot().getChildren().length; i++) {
this.addCategories(categories, (Category) this.getRoot().getChildren()[i]);
}
}
return categories;
}
private void addCategories(List<Category> categories, Category category){
categories.add(category);
for (int i=0; i<category.getChildren().length; i++) {
this.addCategories(categories, (Category) category.getChildren()[i]);
}
}
/**
* Restituisce la categoria corrispondente al codice immesso.
* @param categoryCode Il codice della categoria da restituire.
* @return La categoria richiesta.
*/
@Override
public Category getCategory(String categoryCode) {
Category category = (Category) this.getCategories().get(categoryCode);
return category;
}
@Override
public ITreeNode getNode(String code) {
return this.getCategory(code);
}
@Override
public List<Category> searchCategories(String categoryCodeToken) throws ApsSystemException {
List<Category> searchResult = new ArrayList<Category>();
try {
if (null == this._categories || this._categories.isEmpty()) {
return searchResult;
}
Category root = this.getRoot();
this.searchCategories(root, categoryCodeToken, searchResult);
} catch (Throwable t) {
String message = "Error during searching categories with token " + categoryCodeToken;
_logger.error("Error during searching categories with token {}", categoryCodeToken, t);
throw new ApsSystemException(message, t);
}
return searchResult;
}
@Override
public boolean moveCategory(Category currentCategory, Category newParent) throws ApsSystemException {
boolean resultOperation = false;
_logger.debug("start move category {} under {}", currentCategory, newParent);
try {
this.getCategoryDAO().moveCategory(currentCategory, newParent);
resultOperation = true;
} catch (Throwable t) {
_logger.error("Error while moving page {} under the node {}", currentCategory, newParent, t);
throw new ApsSystemException("Error while moving a category under a different node", t);
}
this.loadCategories();
this.startReloadCategoryReferences(currentCategory.getCode());
return resultOperation;
}
private void searchCategories(Category currentTarget, String categoryCodeToken, List<Category> searchResult) {
if ((null == categoryCodeToken || currentTarget.getCode().toLowerCase().contains(categoryCodeToken.toLowerCase()))) {
searchResult.add(currentTarget);
}
Category[] children = currentTarget.getChildren();
for (int i = 0; i < children.length; i++) {
this.searchCategories(children[i], categoryCodeToken, searchResult);
}
}
@Override
public int getMoveTreeStatus() {
String[] utilizers = this.loadCategoryUtilizers();
if (null == utilizers || utilizers.length == 0) return STATUS_READY;
for (int i = 0; i < utilizers.length; i++) {
String beanName = utilizers[i];
if (null != this._moveTreeStatus && this._moveTreeStatus.containsKey(beanName) && this._moveTreeStatus.get(beanName) == STATUS_RELOADING_REFERENCES_IN_PROGRESS) {
return STATUS_RELOADING_REFERENCES_IN_PROGRESS;
}
}
return STATUS_READY;
}
public int getMoveTreeStatus(String currentBeanName) {
String[] utilizers = this.loadCategoryUtilizers();
if (null == utilizers || utilizers.length == 0) return STATUS_READY;
for (int i = 0; i < utilizers.length; i++) {
String beanName = utilizers[i];
if (beanName.equalsIgnoreCase(currentBeanName)) {
if (null != this._moveTreeStatus && this._moveTreeStatus.containsKey(beanName) && this._moveTreeStatus.get(beanName) == STATUS_RELOADING_REFERENCES_IN_PROGRESS) {
return STATUS_RELOADING_REFERENCES_IN_PROGRESS;
}
}
}
return STATUS_READY;
}
@Override
public Map<String, Integer> getReloadStatus() {
Map<String, Integer> status = new HashMap<String, Integer>();
String[] utilizers = this.loadCategoryUtilizers();
for (int i = 0; i < utilizers.length; i++) {
String beanName = utilizers[i];
status.put(beanName, this.getMoveTreeStatus(beanName));
}
return status;
}
private String[] loadCategoryUtilizers() {
String[] beans = null;
try {
beans = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory)this.getBeanFactory(), CategoryUtilizer.class);
} catch (Throwable t) {
_logger.error("error loading CategoryUtilizer bean names");
throw new RuntimeException(t);
}
return beans;
}
/**
* Entrypoint after category moved
* @param categoryCode
*/
public void startReloadCategoryReferences(String categoryCode) {
if (this.getMoveTreeStatus() == STATUS_READY) {
String[] utilizers = this.loadCategoryUtilizers();
for (int i = 0; i < utilizers.length; i++) {
this.createAndRunReloadCategoryReferencesThread(utilizers[i], categoryCode);
}
} else {
_logger.warn("Attention: Reload Thread not stated. Expexted status {} but now it's {}", STATUS_READY, STATUS_RELOADING_REFERENCES_IN_PROGRESS);
}
}
private Thread createAndRunReloadCategoryReferencesThread(String beanName, String categoryCode) {
ReloadingCategoryReferencesThread reloadThread = null;
int status = this.getMoveTreeStatus(beanName);
if (status == STATUS_READY) {
try {
reloadThread = new ReloadingCategoryReferencesThread(this, beanName, categoryCode);
String threadName = RELOAD_CATEGORY_REFERENCES_THREAD_NAME_PREFIX + this.getName() + "_" + beanName + "_" + DateConverter.getFormattedDate(new Date(), "yyyyMMddHHmmss");
reloadThread.setName(threadName);
reloadThread.start();
_logger.info("Reloading category references for {} started", beanName);
} catch (Throwable t) {
throw new RuntimeException("Error while starting up the category reference reload procedure ", t);
}
} else {
_logger.warn("Reloading category references for {} NOT STARTED: current status {}", beanName, status);
}
return reloadThread;
}
protected synchronized void reloadCategoryReferencesByBeanName(String beanName, String categoryCode) throws ApsSystemException {
if (StringUtils.isBlank(beanName)) {
throw new ApsSystemException("Error: null beanName");
}
this._moveTreeStatus.put(beanName, STATUS_RELOADING_REFERENCES_IN_PROGRESS);
try {
Object service = this.getBeanFactory().getBean(beanName);
if (service != null) {
CategoryUtilizer categoryUtilizer = (CategoryUtilizer) service;
_logger.info("reload category references for {} started at {}", beanName, DateConverter.getFormattedDate(new Date(), "yyyyMMddHHmmss"));
categoryUtilizer.reloadCategoryReferences(categoryCode);
this._moveTreeStatus.put(beanName, STATUS_READY);
_logger.info("reload category references for {} end at {}", beanName, DateConverter.getFormattedDate(new Date(), "yyyyMMddHHmmss"));
}
} catch (Throwable t) {
_logger.error("Reload category references for: {} caused an error", beanName, t);
throw new ApsSystemException("Error reloading entity references by bean: " + beanName, t);
}
}
protected ILangManager getLangManager() {
return _langManager;
}
public void setLangManager(ILangManager langManager) {
this._langManager = langManager;
}
/**
* Restituisce la classe DAO specifica per la gestione
* delle operazioni sulle categorie definite sul db.
* @return La classe DAO specifica.
*/
protected ICategoryDAO getCategoryDAO() {
return this._categoryDao;
}
/**
* Setta la classe DAO specifica per la gestione
* delle operazioni sulle categorie definite sul db.
* @param categoryDao La classe DAO specifica.
*/
public void setCategoryDAO(ICategoryDAO categoryDao) {
this._categoryDao = categoryDao;
}
private ILangManager _langManager;
private Category _root;
private Map<String, Category> _categories;
private ICategoryDAO _categoryDao;
private Map<String, Integer> _moveTreeStatus = new HashMap<String, Integer>();
public static final String RELOAD_CATEGORY_REFERENCES_THREAD_NAME_PREFIX = "RELOAD_CATEGORY_REFERENCES_";
}