PL-8745 Ability to use the same screen in menu twice

This commit is contained in:
Daniil Tsarev 2017-03-31 20:45:29 +04:00
parent 6ed7ee38a4
commit abe1ab2e57
7 changed files with 324 additions and 165 deletions

View File

@ -20,9 +20,9 @@ package com.haulmont.cuba.desktop.sys;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.desktop.gui.components.DesktopComponentsHelper;
import com.haulmont.cuba.desktop.sys.validation.ValidationAwareActionListener;
import com.haulmont.cuba.gui.NoSuchScreenException;
import com.haulmont.cuba.gui.components.KeyCombination;
import com.haulmont.cuba.gui.config.*;
import com.haulmont.cuba.gui.config.MenuCommand;
import com.haulmont.cuba.gui.config.MenuConfig;
import com.haulmont.cuba.gui.config.MenuItem;
import com.haulmont.cuba.gui.logging.UserActionsLogger;
import com.haulmont.cuba.security.global.UserSession;
@ -98,15 +98,7 @@ public class MenuBuilder {
}
private void assignCommand(final JMenuItem jMenuItem, MenuItem item) {
WindowConfig windowConfig = AppBeans.get(WindowConfig.NAME);
WindowInfo windowInfo;
try {
windowInfo = windowConfig.getWindowInfo(item.getId());
} catch (NoSuchScreenException e) {
return;
}
final MenuCommand command = new MenuCommand(item, windowInfo);
final MenuCommand command = new MenuCommand(item);
jMenuItem.addActionListener(new ValidationAwareActionListener() {
@Override
public void actionPerformedAfterValidation(ActionEvent e) {
@ -114,7 +106,7 @@ public class MenuBuilder {
StringBuilder menuPath = new StringBuilder();
formatMenuPath(item, menuPath);
userActionsLog.trace("Window {} was opened using menu item {}", windowInfo.getId(), menuPath.toString());
userActionsLog.trace("Action \"{}\" was performed using menu item {}", command.getCommandDescription(), menuPath.toString());
}
});
}

View File

@ -20,10 +20,7 @@ package com.haulmont.cuba.gui.config;
import com.haulmont.bali.util.Dom4j;
import com.haulmont.cuba.core.app.DataService;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.EntityLoadInfo;
import com.haulmont.cuba.core.global.LoadContext;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.gui.WindowManager;
import com.haulmont.cuba.gui.WindowManager.OpenMode;
@ -32,115 +29,226 @@ import com.haulmont.cuba.gui.WindowManagerProvider;
import com.haulmont.cuba.gui.WindowParams;
import com.haulmont.cuba.gui.components.Window;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.reflect.MethodUtils;
import org.dom4j.Element;
import org.perf4j.StopWatch;
import org.perf4j.log4j.Log4JStopWatch;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class MenuCommand {
private MenuItem item;
private WindowInfo windowInfo;
protected MenuItem item;
public MenuCommand(MenuItem item, WindowInfo windowInfo) {
protected MenuItemCommand command;
public MenuCommand(MenuItem item) {
this.item = item;
this.windowInfo = windowInfo;
createCommand();
}
protected void createCommand() {
if (StringUtils.isNotEmpty(item.getScreen())) {
command = new ScreenCommand(item.getScreen(), item.getDescriptor());
return;
}
if (StringUtils.isNotEmpty(item.getRunnableClass())) {
command = new RunnableClassCommand(item.getRunnableClass());
return;
}
if (StringUtils.isNotEmpty(item.getBean())) {
command = new BeanCommand(item.getBean(), item.getBeanMethod());
}
}
public void execute() {
StopWatch sw = new Log4JStopWatch("MenuItem." + windowInfo.getId());
StopWatch sw = new Log4JStopWatch("MenuItem." + item.getId());
Element descriptor = item.getDescriptor();
Map<String, Object> params = loadParams(descriptor);
OpenType openType = OpenType.NEW_TAB;
String openTypeStr = descriptor.attributeValue("openType");
if (StringUtils.isNotEmpty(openTypeStr)) {
openType = OpenType.valueOf(openTypeStr);
}
WindowManagerProvider wmProvider = AppBeans.get(WindowManagerProvider.NAME);
WindowManager wm = wmProvider.get();
if (openType.getOpenMode() == OpenMode.DIALOG) {
String resizable = descriptor.attributeValue("resizable");
if (StringUtils.isNotEmpty(resizable)) {
openType = openType.resizable(Boolean.parseBoolean(resizable));
}
}
final String id = windowInfo.getId();
if (id.endsWith(Window.CREATE_WINDOW_SUFFIX) || id.endsWith(Window.EDITOR_WINDOW_SUFFIX)) {
Entity entityItem;
if (params.containsKey("item")) {
entityItem = (Entity) params.get("item");
} else {
final String[] strings = id.split("[.]");
String metaClassName;
if (strings.length == 2) {
metaClassName = strings[0];
} else if (strings.length == 3) {
metaClassName = strings[1];
} else {
throw new UnsupportedOperationException();
}
Metadata metadata = AppBeans.get(Metadata.NAME);
entityItem = metadata.create(metaClassName);
}
wm.openEditor(
windowInfo,
entityItem,
openType,
params
);
} else {
wm.openWindow(
windowInfo,
openType,
params
);
}
command.run();
sw.stop();
}
private Map<String, Object> loadParams(Element descriptor) {
Map<String, Object> params = new HashMap<>();
for (Element element : Dom4j.elements(descriptor, "param")) {
String value = element.attributeValue("value");
EntityLoadInfo info = EntityLoadInfo.parse(value);
if (info == null) {
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
Boolean booleanValue = Boolean.valueOf(value);
params.put(element.attributeValue("name"), booleanValue);
} else {
if (value.startsWith("${") && value.endsWith("}")) {
String property = AppContext.getProperty(value.substring(2, value.length() - 1));
if (!StringUtils.isEmpty(property))
value = property;
}
params.put(element.attributeValue("name"), value);
public String getCommandDescription() {
return command.getDescription();
}
protected interface MenuItemCommand extends Runnable {
String getDescription();
}
protected class ScreenCommand implements MenuItemCommand {
protected String screen;
protected Element descriptor;
protected ScreenCommand(String screen, Element descriptor) {
this.screen = screen;
this.descriptor = descriptor;
}
@Override
public void run() {
Map<String, Object> params = loadParams(descriptor);
OpenType openType = OpenType.NEW_TAB;
String openTypeStr = descriptor.attributeValue("openType");
if (StringUtils.isNotEmpty(openTypeStr)) {
openType = OpenType.valueOf(openTypeStr);
}
WindowManager wm = AppBeans.get(WindowManagerProvider.class).get();
if (openType.getOpenMode() == OpenMode.DIALOG) {
String resizable = descriptor.attributeValue("resizable");
if (StringUtils.isNotEmpty(resizable)) {
openType = openType.resizable(Boolean.parseBoolean(resizable));
}
}
WindowInfo windowInfo = AppBeans.get(WindowConfig.class).getWindowInfo(screen);
final String id = windowInfo.getId();
if (id.endsWith(Window.CREATE_WINDOW_SUFFIX) || id.endsWith(Window.EDITOR_WINDOW_SUFFIX)) {
Entity entityItem;
if (params.containsKey("item")) {
entityItem = (Entity) params.get("item");
} else {
final String[] strings = id.split("[.]");
String metaClassName;
if (strings.length == 2) {
metaClassName = strings[0];
} else if (strings.length == 3) {
metaClassName = strings[1];
} else {
throw new UnsupportedOperationException();
}
entityItem = AppBeans.get(Metadata.class).create(metaClassName);
}
wm.openEditor(
windowInfo,
entityItem,
openType,
params
);
} else {
params.put(element.attributeValue("name"), loadEntityInstance(info));
wm.openWindow(
windowInfo,
openType,
params
);
}
}
String caption = MenuConfig.getMenuItemCaption(item.getId());
WindowParams.CAPTION.set(params, caption);
@Override
public String getDescription() {
return String.format("Opening window: \"%s\"", screen);
}
return params;
protected Map<String, Object> loadParams(Element descriptor) {
Map<String, Object> params = new HashMap<>();
for (Element element : Dom4j.elements(descriptor, "param")) {
String value = element.attributeValue("value");
EntityLoadInfo info = EntityLoadInfo.parse(value);
if (info == null) {
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
Boolean booleanValue = Boolean.valueOf(value);
params.put(element.attributeValue("name"), booleanValue);
} else {
if (value.startsWith("${") && value.endsWith("}")) {
String property = AppContext.getProperty(value.substring(2, value.length() - 1));
if (!StringUtils.isEmpty(property))
value = property;
}
params.put(element.attributeValue("name"), value);
}
} else {
params.put(element.attributeValue("name"), loadEntityInstance(info));
}
}
String caption = AppBeans.get(MenuConfig.class).getItemCaption(screen);
WindowParams.CAPTION.set(params, caption);
return params;
}
protected Entity loadEntityInstance(EntityLoadInfo info) {
LoadContext ctx = new LoadContext(info.getMetaClass()).setId(info.getId());
if (info.getViewName() != null) {
ctx.setView(info.getViewName());
}
//noinspection unchecked
return AppBeans.get(DataService.class).load(ctx);
}
}
private Entity loadEntityInstance(EntityLoadInfo info) {
DataService ds = AppBeans.get(DataService.NAME);
LoadContext ctx = new LoadContext(info.getMetaClass()).setId(info.getId());
if (info.getViewName() != null)
ctx.setView(info.getViewName());
Entity entity = ds.load(ctx);
return entity;
protected class BeanCommand implements MenuItemCommand {
protected String bean;
protected String beanMethod;
protected BeanCommand(String bean, String beanMethod) {
this.bean = bean;
this.beanMethod = beanMethod;
}
@Override
public void run() {
Object beanInstance = AppBeans.get(bean);
try {
MethodUtils.invokeMethod(beanInstance, beanMethod, null);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
public String getDescription() {
return String.format("Calling bean method: %s#%s", bean, beanMethod);
}
}
protected class RunnableClassCommand implements MenuItemCommand {
protected String runnableClass;
protected RunnableClassCommand(String runnableClass) {
this.runnableClass = runnableClass;
}
@Override
public void run() {
Class<?> clazz = AppBeans.get(Scripting.class).loadClass(runnableClass);
if (clazz == null) {
throw new IllegalStateException(String.format("Can't load class: %s", runnableClass));
}
if (!Runnable.class.isAssignableFrom(clazz)) {
throw new IllegalStateException(String.format("%s is not instance of Runnable", runnableClass));
}
try {
Object runnableInstance = clazz.newInstance();
((Runnable) runnableInstance).run();
} catch (InstantiationException | IllegalAccessException e) {
throw new DevelopmentException(String.format("Failed to get a new instance of %s", runnableClass));
}
}
@Override
public String getDescription() {
return String.format("Running \"%s\"", runnableClass);
}
}
}

View File

@ -65,8 +65,8 @@ public class MenuConfig {
/**
* Localized menu item caption.
* @param id screen ID as defined in <code>screens.xml</code>
*
* @param id screen ID as defined in <code>screens.xml</code>
* @deprecated Use {@link MenuConfig#getItemCaption(String)}
*/
@Deprecated
@ -180,14 +180,10 @@ public class MenuConfig {
loadDescription(element, menuItem);
loadMenuItems(element, menuItem);
} else if ("item".equals(element.getName())) {
String id = element.attributeValue("id");
if (!StringUtils.isBlank(id)) {
menuItem = new MenuItem(currentParentItem, id);
menuItem.setDescriptor(element);
loadIcon(element, menuItem);
loadShortcut(menuItem, element);
loadStylename(element, menuItem);
loadDescription(element, menuItem);
menuItem = createMenuItem(element, currentParentItem);
if (menuItem == null) {
continue;
}
} else if ("separator".equals(element.getName())) {
String id = element.attributeValue("id");
@ -210,6 +206,69 @@ public class MenuConfig {
}
}
protected MenuItem createMenuItem(Element element, MenuItem currentParentItem) {
String id = element.attributeValue("id");
String idFromActions;
String screen = element.attributeValue("screen");
idFromActions = StringUtils.isNotEmpty(screen) ? screen : null;
String runnableClass = element.attributeValue("class");
checkDuplicateAction(idFromActions, runnableClass);
idFromActions = StringUtils.isNotEmpty(runnableClass) ? runnableClass : idFromActions;
String bean = element.attributeValue("bean");
String beanMethod = element.attributeValue("beanMethod");
if (StringUtils.isNotEmpty(bean) && StringUtils.isEmpty(beanMethod) ||
StringUtils.isEmpty(bean) && StringUtils.isNotEmpty(beanMethod)) {
throw new IllegalStateException("Both bean and beanMethod should be defined.");
}
checkDuplicateAction(idFromActions, bean, beanMethod);
String fqn = bean + "#" + beanMethod;
idFromActions = StringUtils.isNotEmpty(bean) && StringUtils.isNotEmpty(beanMethod) ? fqn : idFromActions;
if (StringUtils.isEmpty(id) && StringUtils.isEmpty(idFromActions)) {
throw new IllegalStateException("MenuItem should have at least one action");
}
if (StringUtils.isEmpty(id) && StringUtils.isNotEmpty(idFromActions)) {
id = idFromActions;
}
if (StringUtils.isNotEmpty(id) && StringUtils.isEmpty(idFromActions)) {
screen = id;
}
MenuItem menuItem = new MenuItem(currentParentItem, id);
menuItem.setScreen(screen);
menuItem.setRunnableClass(runnableClass);
menuItem.setBean(bean);
menuItem.setBeanMethod(beanMethod);
menuItem.setDescriptor(element);
loadIcon(element, menuItem);
loadShortcut(menuItem, element);
loadStylename(element, menuItem);
loadDescription(element, menuItem);
return menuItem;
}
protected void checkDuplicateAction(String menuItemId, String... actionDefinition) {
boolean actionDefined = true;
for (String s : actionDefinition) {
actionDefined &= StringUtils.isNotEmpty(s);
}
if (StringUtils.isNotEmpty(menuItemId) && actionDefined) {
throw new IllegalStateException("MenuItem can't have more than one action.");
}
}
protected void loadExpanded(Element element, MenuItem menuItem) {
String expanded = element.attributeValue("expanded");
if (StringUtils.isNotEmpty(expanded)) {

View File

@ -35,6 +35,10 @@ public class MenuItem {
private List<MenuItem> children = new ArrayList<>();
private String id;
private String screen;
private String runnableClass;
private String bean;
private String beanMethod;
private String stylename;
private String icon;
private String description;
@ -152,4 +156,36 @@ public class MenuItem {
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getRunnableClass() {
return runnableClass;
}
public void setRunnableClass(String runnableClass) {
this.runnableClass = runnableClass;
}
public String getBean() {
return bean;
}
public void setBean(String bean) {
this.bean = bean;
}
public String getBeanMethod() {
return beanMethod;
}
public void setBeanMethod(String beanMethod) {
this.beanMethod = beanMethod;
}
}

View File

@ -56,6 +56,10 @@
<xs:element type="permissionsType" name="permissions" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
<xs:attribute type="xs:string" name="id"/>
<xs:attribute type="xs:string" name="screen"/>
<xs:attribute type="xs:string" name="class"/>
<xs:attribute type="xs:string" name="bean"/>
<xs:attribute type="xs:string" name="beanMethod"/>
<xs:attribute type="openTypeType" name="openType"/>
<xs:attribute type="xs:boolean" name="resizable"/>
<xs:attribute type="xs:string" name="shortcut"/>

View File

@ -16,13 +16,15 @@
package com.haulmont.cuba.web.sys;
import com.haulmont.cuba.core.global.DevelopmentException;
import com.haulmont.cuba.core.global.Scripting;
import com.haulmont.cuba.gui.ComponentsHelper;
import com.haulmont.cuba.gui.NoSuchScreenException;
import com.haulmont.cuba.gui.components.KeyCombination;
import com.haulmont.cuba.gui.components.Window;
import com.haulmont.cuba.gui.components.mainwindow.AppMenu;
import com.haulmont.cuba.gui.config.*;
import com.haulmont.cuba.gui.config.MenuCommand;
import com.haulmont.cuba.gui.config.MenuConfig;
import com.haulmont.cuba.gui.config.MenuItem;
import com.haulmont.cuba.gui.config.WindowConfig;
import com.haulmont.cuba.security.global.UserSession;
import com.haulmont.cuba.web.toolkit.MenuShortcutAction;
import com.vaadin.event.ShortcutListener;
@ -58,6 +60,9 @@ public class MenuBuilder {
@Inject
protected WindowConfig windowConfig;
@Inject
protected Scripting scripting;
protected AppMenu appMenu;
public MenuBuilder() {
@ -172,34 +177,10 @@ public class MenuBuilder {
if (CollectionUtils.isNotEmpty(item.getChildren()) || item.isMenu()) //check item is menu
return null;
WindowInfo windowInfo = null;
if (!item.isSeparator()) {
try {
windowInfo = windowConfig.getWindowInfo(item.getId());
} catch (NoSuchScreenException e) {
log.error("Invalid screen ID for menu item: " + item.getId());
}
}
MenuCommand menuCommand = new MenuCommand(item);
final MenuCommand command;
if (windowInfo != null) {
command = new MenuCommand(item, windowInfo);
} else {
command = null;
}
return selectedItem -> {
if (command != null) {
command.execute();
} else {
if (item.getParent() != null) {
throw new DevelopmentException("Invalid screen ID for menu item: " + item.getId(),
"Parent menu ID", item.getParent().getId());
} else {
throw new DevelopmentException("Invalid screen ID for menu item: " + item.getId());
}
}
};
return menuItem ->
menuCommand.execute();
}
protected boolean isMenuItemEmpty(AppMenu.MenuItem menuItem) {

View File

@ -17,13 +17,14 @@
package com.haulmont.cuba.web.sys;
import com.google.common.base.Strings;
import com.haulmont.cuba.core.global.DevelopmentException;
import com.haulmont.cuba.gui.ComponentsHelper;
import com.haulmont.cuba.gui.NoSuchScreenException;
import com.haulmont.cuba.gui.components.KeyCombination;
import com.haulmont.cuba.gui.components.Window;
import com.haulmont.cuba.gui.components.mainwindow.SideMenu;
import com.haulmont.cuba.gui.config.*;
import com.haulmont.cuba.gui.config.MenuCommand;
import com.haulmont.cuba.gui.config.MenuConfig;
import com.haulmont.cuba.gui.config.MenuItem;
import com.haulmont.cuba.gui.config.WindowConfig;
import com.haulmont.cuba.security.global.UserSession;
import com.vaadin.event.ShortcutListener;
import com.vaadin.ui.AbstractComponent;
@ -171,32 +172,10 @@ public class SideMenuBuilder {
if (!item.getChildren().isEmpty() || item.isMenu()) //check item is menu
return null;
WindowInfo windowInfo = null;
try {
windowInfo = windowConfig.getWindowInfo(item.getId());
} catch (NoSuchScreenException e) {
log.error("Invalid screen ID for menu item: " + item.getId());
}
MenuCommand command = new MenuCommand(item);
final MenuCommand command;
if (windowInfo != null) {
command = new MenuCommand(item, windowInfo);
} else {
command = null;
}
return event -> {
if (command != null) {
return event ->
command.execute();
} else {
if (item.getParent() != null) {
throw new DevelopmentException("Invalid screen ID for menu item: " + item.getId(),
"Parent menu ID", item.getParent().getId());
} else {
throw new DevelopmentException("Invalid screen ID for menu item: " + item.getId());
}
}
};
}
protected boolean isMenuItemEmpty(SideMenu.MenuItem menuItem) {