/* * 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.view.action; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.jspresso.framework.security.ISecurable; import org.jspresso.framework.security.ISecurityHandler; import org.jspresso.framework.util.automation.IPermIdSource; import org.jspresso.framework.util.gui.ERenderingOptions; import org.jspresso.framework.util.lang.ICloneable; /** * An action map is used to structure a set of actions. The actions are not * directly registered but are placed into action lists that in turn are * assigned to the action map. Action maps can be merged together, i.e. an * action map can have multiple parent action maps. In that case, Action lists * that have the same name are merged together. The way action maps are rendered * depends on the place where they are declared. For instance an action map that * is assigned to a view might be rendered as a toolbar with each action list * separated into its own group. On the other hand, an action map declared on * the frontend controller might be represented as a menu bar on the main * application frame. * * @author Vincent Vandenschrick */ public class ActionMap implements ISecurable, IPermIdSource, ICloneable { private List<ActionList> actionLists; private List<ActionMap> parentActionMaps; private Collection<String> grantedRoles; private ERenderingOptions renderingOptions; private Boolean hideActionWhenDisabled; private String permId; private static void completeActionMap(Map<String, ActionList> bufferActionMap, List<ActionList> actionLists, ISecurityHandler securityHandler) { if (actionLists != null) { Map<String, ActionList> mapOfActionLists = new LinkedHashMap<>(); for (ActionList al : actionLists) { if (al.getName() != null) { mapOfActionLists.put(al.getName(), al); } else { // to avoid duplicates mapOfActionLists.put(Integer.toHexString(al.hashCode()), al); } } for (Map.Entry<String, ActionList> actionListEntry : mapOfActionLists.entrySet()) { if (securityHandler.isAccessGranted(actionListEntry.getValue())) { try { securityHandler.pushToSecurityContext(actionListEntry.getValue()); ActionList bufferActionList = bufferActionMap.get(actionListEntry.getKey()); if (bufferActionList == null) { bufferActionList = actionListEntry.getValue().clone(); bufferActionMap.put(actionListEntry.getKey(), bufferActionList); } else { for (IDisplayableAction localAction : actionListEntry.getValue().getActions()) { int existingIndex = bufferActionList.getActions().indexOf(localAction); if (existingIndex >= 0) { bufferActionList.getActions().set(existingIndex, localAction); } else { bufferActionList.getActions().add(localAction); } } } } finally { securityHandler.restoreLastSecurityContextSnapshot(); } } } } } /** * Gets the list of action sets composing the parent action maps with the * local one. * * @param securityHandler * the action handler used to handle role based security. * @return the actions list. */ public List<ActionList> getActionLists(ISecurityHandler securityHandler) { Map<String, ActionList> buffer = new LinkedHashMap<>(); if (parentActionMaps != null) { for (ActionMap parentActionMap : parentActionMaps) { if (securityHandler.isAccessGranted(parentActionMap)) { try { securityHandler.pushToSecurityContext(parentActionMap); completeActionMap(buffer, parentActionMap.getActionLists(securityHandler), securityHandler); } finally { securityHandler.restoreLastSecurityContextSnapshot(); } } } } if (actionLists != null) { completeActionMap(buffer, actionLists, securityHandler); } return new ArrayList<>(buffer.values()); } /** * Gets the renderingOptions. * * @return the renderingOptions. */ public ERenderingOptions getRenderingOptions() { return renderingOptions; } /** * Assigns the action lists that are directly owned by this action map. The * action lists that will actually be rendered will be the merge of the * directly owned action lists and of the parent action maps. Action lists * that have the same name are merged together and into a merged action list, * local actions will replace parent actions with the same name. * * @param actionLists * the action lists list to set. */ public void setActionLists(List<ActionList> actionLists) { this.actionLists = actionLists; } /** * Assigns the parent action maps. The action lists that will actually be * rendered will be the merge of the directly owned action lists and of the * parent action maps. Action lists that have the same name are merged * together and into a merged action list, local actions will replace parent * actions with the same name. * * @param parentActionMaps * the parentActionMaps to set. */ public void setParentActionMaps(List<ActionMap> parentActionMaps) { this.parentActionMaps = parentActionMaps; } /** * Indicates how the actions should be rendered. This is either a value of the * {@code ERenderingOptions} enum or its equivalent string representation * : * <ul> * <li>{@code LABEL_ICON} for label and icon</li> * <li>{@code LABEL} for label only</li> * <li>{@code ICON} for icon only.</li> * </ul> * <p> * Default value is {@code null}, i.e. determined from outside, e.g. the * view factory. * * @param renderingOptions * the renderingOptions to set. */ public void setRenderingOptions(ERenderingOptions renderingOptions) { this.renderingOptions = renderingOptions; } /** * Gets the grantedRoles. * * @return the grantedRoles. */ @Override public Collection<String> getGrantedRoles() { return grantedRoles; } /** * Assigns the roles that are authorized to use this action map. It supports * "<b>!</b>" prefix to negate the role(s). Whenever the user is not * granted sufficient privileges, the action map is simply not displayed at * runtime. Setting the collection of granted roles to {@code null} * (default value) disables role based authorization, then access is granted * to anyone. * * @param grantedRoles * the grantedRoles to set. */ public void setGrantedRoles(Collection<String> grantedRoles) { this.grantedRoles = grantedRoles; } /** * Gets the permId. * * @return the permId. */ @Override public String getPermId() { return permId; } /** * Sets the permanent identifier to this application element. Permanent * identifiers are used by different framework parts, like dynamic security or * record/replay controllers to uniquely identify an application element. * Permanent identifiers are generated by the SJS build based on the element * id but must be explicitly set if Spring XML is used. * * @param permId * the permId to set. */ @Override public void setPermId(String permId) { this.permId = permId; } /** * {@inheritDoc} */ @Override public ActionMap clone() { ActionMap clone = null; try { clone = (ActionMap) super.clone(); } catch (CloneNotSupportedException e) { // Cannot happen; } return clone; } /** * When configured to {@code true}, the actions of the action map are hidden when they are disabled. Default value is * undefined, i.e. {@code null}, meaning that the view factory configuration will drive the property. * * @return the boolean */ public Boolean getHideActionWhenDisabled() { return hideActionWhenDisabled; } /** * Sets hidden when disabled. * * @param hideActionWhenDisabled * the hidden when disabled */ public void setHideActionWhenDisabled(Boolean hideActionWhenDisabled) { this.hideActionWhenDisabled = hideActionWhenDisabled; } }