/* * Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved. * * This file is part of the Jspresso framework. * * Jspresso 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 3 of the License, or * (at your option) any later version. * * Jspresso 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. * * You should have received a copy of the GNU Lesser General Public License * along with Jspresso. If not, see <http://www.gnu.org/licenses/>. */ package org.jspresso.framework.application.backend.action; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import org.jspresso.framework.application.action.AbstractAction; import org.jspresso.framework.application.backend.IBackendController; import org.jspresso.framework.application.backend.async.AsyncActionExecutor; import org.jspresso.framework.application.backend.session.IApplicationSession; import org.jspresso.framework.binding.IValueConnector; import org.jspresso.framework.model.component.IComponent; import org.jspresso.framework.model.entity.IEntity; import org.jspresso.framework.model.entity.IEntityFactory; import org.jspresso.framework.util.accessor.IAccessorFactory; import org.jspresso.framework.view.IView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.transaction.support.TransactionTemplate; /** * This class should serve as base class for implementing actions that execute * on the backend (domain model) of the application. It provides accessors on * the context elements that are generally used through the action execution * process. * * @author Vincent Vandenschrick */ public class BackendAction extends AbstractAction { private static final Logger LOG = LoggerFactory .getLogger(BackendAction.class); private static final String WARN_BAD_ACCESS_DISABLED = "WARN_BAD_ACCESS_DISABLED"; /** * {@code SELECTED_MODEL} is a static context key to inject into the test * context in order to bypass the query to the view to retrieve the selected * model. */ public static final String SELECTED_MODEL = "SELECTED_MODEL"; /** * {@code SELECTED_MODELS} is a static context key to inject into the * test context in order to bypass the query to the view to retrieve the * selected models. */ public static final String SELECTED_MODELS = "SELECTED_MODELS"; private boolean badFrontendAccessChecked; /** * Constructs a new {@code BackendAction} instance. */ public BackendAction() { setBadFrontendAccessChecked(true); } /** * {@inheritDoc} */ @Override public boolean isBackend() { return true; } /** * Gets the accessorFactory. * * @param context * the action context. * @return the accessorFactory. */ protected IAccessorFactory getAccessorFactory(Map<String, Object> context) { return getController(context).getAccessorFactory(); } /** * Gets the current application session. * * @param context * the action context. * @return the current application session. */ protected IApplicationSession getApplicationSession( Map<String, Object> context) { return getController(context).getApplicationSession(); } /** * Performs necessary cleanings when an entity or component is deleted. * * @param component * the deleted entity or component. * @param context * The action context. * @param dryRun * set to true to simulate before actually doing it. * @throws IllegalAccessException * whenever this kind of exception occurs. * @throws java.lang.reflect.InvocationTargetException * whenever this kind of exception occurs. * @throws NoSuchMethodException * whenever this kind of exception occurs. */ protected void cleanRelationshipsOnDeletion(IComponent component, Map<String, Object> context, boolean dryRun) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { getController(context).cleanRelationshipsOnDeletion(component, dryRun); } /** * Gets the frontend controller out of the action context. * * @param context * the action context. * @return the frontend controller. */ @Override protected IBackendController getController(Map<String, Object> context) { return getBackendController(context); } /** * Gets the entityFactory. * * @param context * the action context. * @return the entityFactory. */ protected IEntityFactory getEntityFactory(Map<String, Object> context) { return getController(context).getEntityFactory(); } /** * Gets the transactionTemplate. * * @param context * the action context. * @return the transactionTemplate. */ protected TransactionTemplate getTransactionTemplate( Map<String, Object> context) { return getController(context).getTransactionTemplate(); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override protected <T> T getSelectedModel(int[] viewPath, Map<String, Object> context) { boolean wbad = context.containsKey(WARN_BAD_ACCESS_DISABLED); try { if (viewPath == null) { // we don't warn about anything if we only query the selected model // since it's supported now by injecting a SELECTED_MODEL variable in // the context during testing. if (context.containsKey(SELECTED_MODEL)) { return (T) context.get(SELECTED_MODEL); } context.put(WARN_BAD_ACCESS_DISABLED, null); } return super.getSelectedModel(viewPath, context); } finally { if (!wbad) { context.remove(WARN_BAD_ACCESS_DISABLED); } } } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") @Override protected <T> List<T> getSelectedModels(int[] viewPath, Map<String, Object> context) { boolean wbad = context.containsKey(WARN_BAD_ACCESS_DISABLED); try { if (viewPath == null) { // we don't warn about anything if we only query the selected model // since it's supported now by injecting a SELECTED_MODELS variable in // the context during testing. if (context.containsKey(SELECTED_MODELS)) { return (List<T>) context.get(SELECTED_MODELS); } context.put(WARN_BAD_ACCESS_DISABLED, null); } return super.getSelectedModels(viewPath, context); } finally { if (!wbad) { context.remove(WARN_BAD_ACCESS_DISABLED); } } } /** * {@inheritDoc} */ @Override protected <T> IView<T> getView(int[] viewPath, Map<String, Object> context) { warnBadFrontendAccess(context); return super.getView(viewPath, context); } /** * {@inheritDoc} */ @Override protected IValueConnector getViewConnector(int[] viewPath, Map<String, Object> context) { warnBadFrontendAccess(context); return super.getViewConnector(viewPath, context); } /** * Allows to disable frontend access checks from backend actions. This is * sometimes necessary to avoid over-complicated refactoring when the access * is accepted by the dev team. * * @return {@code true} by default, i.e. bad frontend access detection is * enabled. */ protected boolean isBadFrontendAccessChecked() { return badFrontendAccessChecked; } /** * Sets the badFrontendAccessChecked. * * @param badFrontendAccessChecked * the badFrontendAccessChecked to set. */ public void setBadFrontendAccessChecked(boolean badFrontendAccessChecked) { this.badFrontendAccessChecked = badFrontendAccessChecked; } private void warnBadFrontendAccess(Map<String, Object> context) { if (isBadFrontendAccessChecked() && !context.containsKey(WARN_BAD_ACCESS_DISABLED)) { LOG.warn( "Access to frontend context detected from a backend action which is strongly discouraged. " + "{} should use either the action parameter or a specific variable.", getClass().getName()); } } /** * Inform about the action progress. * * @param progress * the action progress. */ protected void setProgress(double progress) { if (Thread.currentThread() instanceof AsyncActionExecutor) { ((AsyncActionExecutor) Thread.currentThread()).setProgress(progress); } } /** * Reloads an entity in mongo. * * @param entity * the entity to reload. * @param context * the action context. */ protected void reloadEntity(IEntity entity, Map<String, Object> context) { getController(context).reload(entity); } }