WebpagesDatatable.java

package sk.iway.iwcm.editor.rest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import sk.iway.iwcm.Constants;
import sk.iway.iwcm.InitServlet;
import sk.iway.iwcm.RequestBean;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.doc.DocDB;
import sk.iway.iwcm.doc.DocDetails;
import sk.iway.iwcm.doc.DocDetailsRepository;
import sk.iway.iwcm.doc.GroupDetails;
import sk.iway.iwcm.doc.GroupsDB;
import sk.iway.iwcm.doc.attributes.jpa.DocAtrDefRepository;
import sk.iway.iwcm.editor.facade.EditorFacade;
import sk.iway.iwcm.editor.service.WebpagesService;
import sk.iway.iwcm.i18n.Prop;
import sk.iway.iwcm.system.datatable.DatatablePageImpl;
import sk.iway.iwcm.system.datatable.DatatableRestControllerV2;
import sk.iway.iwcm.system.datatable.NotifyBean;
import sk.iway.iwcm.system.datatable.ProcessItemAction;
import sk.iway.iwcm.users.UserDetails;

/**
 * Base for all REST controlers for webpages/based on web-pages-datatable.js
 */
public class WebpagesDatatable extends DatatableRestControllerV2<DocDetails, Long> {

    protected final DocDetailsRepository docDetailsRepository;
    protected final EditorFacade editorFacade;
    protected final DocAtrDefRepository docAtrDefRepository;

    @Autowired
    public WebpagesDatatable(DocDetailsRepository docDetailsRepository, EditorFacade editorFacade, DocAtrDefRepository docAtrDefRepository) {
        super(docDetailsRepository);
        this.docDetailsRepository = docDetailsRepository;
        this.editorFacade = editorFacade;
        this.docAtrDefRepository = docAtrDefRepository;
    }

    @Override
    public Page<DocDetails> getAllItems(Pageable pageable) {
        GetAllItemsDocOptions options = getDefaultOptions(pageable, true);

        if(isSearchVersion()) {
            //Do not test perms for groupId, it's test later
            DatatablePageImpl<DocDetails> pageImpl = new DatatablePageImpl<>(getAllItemsIncludeSpecSearch(new DocDetails(), pageable));
            WebpagesService.addOptions(pageImpl, options);
            return pageImpl;
        }

        return WebpagesService.getAllItems(options);
    }

    @Override
    public DocDetails editItem(DocDetails entity, long id) {

        DocDetails original = DocDB.getInstance().getDoc(entity.getDocId(), -1, false);
        DocDetails saved = editorFacade.save(entity);

        List<UserDetails> approveByUsers = editorFacade.getApprovers();
        addInsertEditNotify(false, approveByUsers);

        if (editorFacade.isForceReload()) setForceReload(true);

        if (isRefreshMenuRequired(original, saved)) setForceReload(true);

        addNotify(editorFacade.getNotify());

        if (RequestBean.getAttribute("forceReloadTree")!=null) setForceReload(true);

        return saved;
    }

    @Override
    public DocDetails insertItem(DocDetails entity) {
        //Cant insert in TRASH group
        Prop propSystem = Prop.getInstance(Constants.getString("defaultLanguage"));
        String trashDirName = propSystem.getText("config.trash_dir");
        if(entity.getEditorFields() != null && entity.getEditorFields().getGroupDetails() != null && entity.getEditorFields().getGroupDetails().getFullPath().startsWith(trashDirName) ) { //starts with so it block insert in trash and child folders
            addNotify( new NotifyBean(getProp().getText("webpage.insert_into_trash.title"), getProp().getText("webpage.insert_into_trash.text"), NotifyBean.NotifyType.ERROR, 60000) );
            return null;
        }

        DocDetails saved = editorFacade.save(entity);
        addNotify(editorFacade.getNotify());

        //ak je zapnute zobrazenie zoznamu stranok pre novu stranku musim spravit reload
        //ostatne ako zmena adresara vyvolava reload uz standardne
        if (WebpagesService.isTreeShowPages(getUser())) setForceReload(true);

        List<UserDetails> approveByUsers = editorFacade.getApprovers();
        addInsertEditNotify(true, approveByUsers);

        if (RequestBean.getAttribute("forceReloadTree")!=null) setForceReload(true);

        return saved;
    }

    @Override
    public DocDetails getOneItem(long id) {
        int groupId = Tools.getIntValue(getRequest().getParameter("groupId"), Constants.getInt("rootGroupId"));
        int historyId = Tools.getIntValue(getRequest().getParameter("historyId"), -1);

        List<NotifyBean> notifyList = new ArrayList<>();
        DocDetails docToReturn = WebpagesService.getOneItem(id, groupId, historyId, editorFacade, docAtrDefRepository, notifyList, getRequest());
        addNotify(notifyList);

        return docToReturn;
    }

    @Override
    public boolean deleteItem(DocDetails entity, long id) {
        boolean deleted = editorFacade.delete(entity);
        addNotify(editorFacade.getNotify());

        //ak je zapnute zobrazenie zoznamu stranok pre novu stranku musim spravit reload
        //ostatne ako zmena adresara vyvolava reload uz standardne
        if (WebpagesService.isTreeShowPages(getUser())) setForceReload(true);

        return deleted;
    }

    @Override
    public void addSpecSearch(Map<String, String> params, List<Predicate> predicates, Root<DocDetails> root, CriteriaBuilder builder) {

        WebpagesService.addSpecSearch(params, predicates, root, builder, getUser());

        super.addSpecSearch(params, predicates, root, builder);
    }

    @Override
    public DocDetails processFromEntity(DocDetails entity, ProcessItemAction action, int rowCount) {
        return (DocDetails)WebpagesService.processFromEntity(entity, action, getRequest(), rowCount == 1);
    }

    @Override
    public Page<DocDetails> findByColumns(@RequestParam Map<String, String> params, Pageable pageable, DocDetails search) {

        Integer groupId = Tools.getIntValue(getRequest().getParameter("groupId"), Constants.getInt("rootGroupId"));

        //ak chcem zobrazit recentPages
        if(groupId == Constants.getInt("systemPagesRecentPages")) {

            //Key groupId (and other) must be removed because we set this params in special way inside getAllItems method
            params.remove("groupId");
            params.remove("size");
            params.remove("page");
            params.remove("sort");

            BeanWrapperImpl searchWrapped = new BeanWrapperImpl(search);
            final Map<String, String> searchProperties = new HashMap<>();
            getSearchProperties(params, searchProperties, searchWrapped, null, false);

            //Get specification from columns params
            Specification<DocDetails> columnsSpecification = getSearchConditions(searchProperties, params, search);

            GetAllItemsDocOptions options = new GetAllItemsDocOptions(getRequest());

            options.setGroupId(groupId);
            options.setUserGroupId(Tools.getIntValue(getRequest().getParameter("userGroupId"), -1));
            options.setPageable(pageable);
            options.setDocDetailsRepository(docDetailsRepository);
            options.setDocAtrDefRepository(docAtrDefRepository);
            options.setColumnsSpecification(columnsSpecification);

            //If second param columnsSpecification is set, method will join columnsSpecification to created specification by method
            Page<DocDetails> page = WebpagesService.getAllItems(options);
            return page;
        }

        return super.findByColumns(params, pageable, search);
    }

    /**
     * Ulozi do session DocDetails vytvoreny z JSON objektu, ktory sa nasledne pouzije
     * v PreviewController pre vytvorenie nahladu web stranky
     * Je to takto, lebo inak som nevedel preniest JSON data z editora priamo do
     * PreviewControllera
     */
    @PostMapping(value="/preview/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public String preview(@RequestBody DocDetails entity, HttpServletRequest request) {

        entity.getEditorFields().toDocDetails(entity);

        setPreviewObject(entity, request);
        return "{\"ok\": true}";
    }

    @Override
    public void afterSave(DocDetails entity, DocDetails saved) {
        setPreviewObject(saved, getRequest());
    }

    /**
     * Ulozi do session DocDetails objekt pre nahlad stranky
     * @param entity
     * @param request
     */
    private void setPreviewObject(DocDetails entity, HttpServletRequest request) {
        request.getSession().setAttribute("ShowdocAction.showDocData", entity);
    }

    @Override
    public boolean processAction(DocDetails entity, String action) {
        if ("saveAsAbTest".equals(action)) {
            //sprav kopiu entity a uloz ako B variant
            DocDetails saved = editorFacade.saveAsBVariant(entity);
            if (saved != null) setForceReload(true);

            addNotify(editorFacade.getNotify());

            return true;
        } else if("recoverDoc".equals(action)) {
            editorFacade.recoverWebpageFromTrash(entity);

            addNotify(editorFacade.getNotify());

            return true;
        }

        return false;
    }


    /**
     * Overi, ci je potrebne obnovit aj stromovu strukturu (ak su zobrazene aj stranky) v jstree
     * @param entity
     * @param saved
     * @return
     */
    protected boolean isRefreshMenuRequired(DocDetails entity, DocDetails saved) {
        if (WebpagesService.isTreeShowPages(getUser())==false) return false;

		if (entity.getDocId() < 1) return true;

		if (saved != null) {
			if (saved.getTitle().equals(entity.getTitle()) == false) return true;
			if (saved.getExternalLink().equals(entity.getExternalLink()) == false) return true;
			if (saved.getVirtualPath().equals(entity.getVirtualPath()) == false) return true;
			if (saved.isAvailable() != entity.isAvailable()) return true;
			if (saved.getGroupId() != entity.getGroupId()) return true;
			if (saved.getSortPriority() != entity.getSortPriority()) return true;
		}

		return false;
	}

    @Override
    public void afterDuplicate(DocDetails entity, Long originalId) {
        if (originalId!=null && entity.getDocId()>0) editorFacade.duplicateMedia(originalId.intValue(), entity.getDocId());
    }

    /**
     * Show notification to user if action need to be appoved by approver by sending email ( + approver full name).
     * @param isInsert - true if its insert, false if its edit (change notify text)
     * @param approveByUsers - list of users that one of them need approve this action
     */
    protected void addInsertEditNotify(boolean isInsert, List<UserDetails> approveByUsers) {
        String needApproveText = isInsert ? "editor.approveRequestInsert" : "editor.approveRequestGet";

        if(approveByUsers != null && approveByUsers.isEmpty()==false) {
            StringBuilder userNames = new StringBuilder();
            for (UserDetails approveUser : approveByUsers) {
                if (userNames.length()>0) userNames.append(", ");
                userNames.append(approveUser.getFullName());
            }

            NotifyBean notify = new NotifyBean(getProp().getText("editor.approve.notifyTitle"), getProp().getText(needApproveText)+": "+userNames.toString(), NotifyBean.NotifyType.INFO, 60000);
            addNotify(notify);
        } else {
            if (editorFacade.isPageSavedAsWorkVersion()) {
                NotifyBean notify = new NotifyBean(getProp().getText("text.info"), getProp().getText("editor.pageSaved"), NotifyBean.NotifyType.INFO, 15000);
                addNotify(notify);
            }

            if (editorFacade.getPublihStart() != null) {
                String publishDateTime = Tools.formatDateTimeSeconds(editorFacade.getPublihStart());
                NotifyBean notify = new NotifyBean(getProp().getText("text.warning"), getProp().getText("editor.publish.pagesaved")+" "+publishDateTime, NotifyBean.NotifyType.WARNING, 30000);
                addNotify(notify);
            }
        }
    }

    @Override
    public boolean checkItemPerms(DocDetails entity, Long id) {
        if (InitServlet.isTypeCloud() && entity.getDocId()>0) {
            DocDetails old = DocDB.getInstance().getBasicDocDetails(entity.getDocId(), false);
            if (old != null) {
                GroupDetails group = GroupsDB.getInstance().getGroup(old.getGroupId());
                if (group != null && GroupsDB.isGroupEditable(getUser(), group.getGroupId())==false) return false;
            }
        }
        return true;
    }

    /**
     * Prepare options for getAllItems method, read parameters from request
     * - groupId, if not present, set to Constants.rootGroupId
     * - userGroupId, if not present, set to -1
     * - recursive, if not present, set to false
     * @param pageable
     * @param checkPerms - true to verify user permissions for groupId
     * @return
     */
    public GetAllItemsDocOptions getDefaultOptions(Pageable pageable, boolean checkPerms) {
        GetAllItemsDocOptions options = new GetAllItemsDocOptions(getRequest());

        int groupId = Tools.getIntValue(getRequest().getParameter("groupId"), Constants.getInt("rootGroupId"));
        getRequest().getSession().setAttribute(Constants.SESSION_GROUP_ID, String.valueOf(groupId));

        options.setGroupId(groupId);
        options.setUserGroupId(Tools.getIntValue(getRequest().getParameter("userGroupId"), -1));
        options.setPageable(pageable);

        options.setDocDetailsRepository(docDetailsRepository);
        options.setDocAtrDefRepository(docAtrDefRepository);

        options.setTempId(Tools.getIntValue(getRequest().getParameter("tempId"), -1));

        if("true".equals(getRequest().getParameter("recursive"))) options.setRecursiveSubfolders(true);

        if (checkPerms) {
            if (options.getGroupId()!=Constants.getInt("systemPagesRecentPages") && GroupsDB.isGroupEditable(getUser(), options.getGroupId())==false && GroupsDB.isGroupViewable(getUser(), options.getGroupId())==false) {
                throwError("components.jstree.access_denied__group");
            }
        }
        return options;
    }

    private boolean isSearchVersion() {
        return "true".equals(getRequest().getParameter("isSearchVersion"));
    }
}