I have implemented menu hiding based on user authentication and roles using DynamicMenuContent
/**
* Presenter that displays menu item if, and only if the user has the specified role.
*/
public class RoleMenuPresenter
extends JMenuItem
implements DynamicMenuContent {
private static final JComponent[] EMPTY = new JComponent[0];
private final String roleName;
private final AuthenticationDelegate delegate = (AuthenticationDelegate) SpringSingleton
.getBean("authenticationDelegate");
private final JComponent[] components = {this};
private final JComponent[] emptyComponents = EMPTY;
//~ Constructors ===================================================================================================
/**
* Constructs this presenter.
*
* @param a action to execute
* @param roleName the rolename
*/
public RoleMenuPresenter(Action a, String roleName) {
super(a);
this.roleName = roleName;
}
//~ Methods ========================================================================================================
@Override
public JComponent[] getMenuPresenters() {
if (delegate.getStatusBean().hasRole(roleName)) {
return components;
}
return emptyComponents;
}
@Override
public JComponent[] synchMenuPresenters(JComponent[] jComponents) {
return getMenuPresenters();
}
public String getRoleName() {
return roleName;
}
public interface Provider
extends Presenter.Menu {
@Override
RoleMenuPresenter getMenuPresenter();
}
}
Secured Action
implements RoleMenuPresenter.Provider
. Menu items are hidden as expected. There is a problem however, if all items of a menu are hidden, the menu still is displayed. Example:
If A, B, and C are hidden, Transaction is still visible. My question is how to automatically hide Transaction? Thank you.
I finally managed to make it work. MenuFolderUtil
is used to calculate empty folder paths. ReheatMenus
is used to warm up the menus. DynamicLayerContent
is file system provider to provide in memory *_hidden
folders. MenuVisibilityUtil
is the main entry.
final class MenuFolderUtil {
static Set<String> findEmptyFolder() {
Set<String> folderPaths = new LinkedHashSet<>();
FileObject file = FileUtil.getConfigFile("Menu");
Enumeration<? extends FileObject> folderEnumerations = file.getFolders(
true);
while (folderEnumerations.hasMoreElements()) {
FileObject element = folderEnumerations.nextElement();
folderPaths.add(element.getPath());
}
Enumeration<? extends FileObject> childrenEnum = file.getChildren(true);
while (childrenEnum.hasMoreElements()) {
FileObject element = childrenEnum.nextElement();
if (element.isFolder()) {
continue;
}
FileObject parent = element.getParent();
String parentPath = parent.getPath();
if (!folderPaths.contains(parentPath)) {
continue;
}
Action action = FileUtil.getConfigObject(
element.getPath(),
Action.class);
if (action instanceof Presenter.Menu) {
JMenuItem presenter = ((Presenter.Menu) action).getMenuPresenter();
if (presenter instanceof DynamicMenuContent) {
treatDynamicMenuContent(
(DynamicMenuContent) presenter,
folderPaths, buildParentPaths(element));
} else {
folderPaths.removeAll(buildParentPaths(element));
}
} else if (action instanceof DynamicMenuContent) {
treatDynamicMenuContent(
(DynamicMenuContent) action,
folderPaths,
buildParentPaths(element));
} else if (action != null) {
folderPaths.removeAll(buildParentPaths(element));
}
}
return folderPaths;
}
private static void treatDynamicMenuContent(DynamicMenuContent presenter,
Set<String> folderPaths, Set<String> c) {
JComponent[] presenters = presenter.getMenuPresenters();
if (presenters != null && presenters.length > 0) {
folderPaths.removeAll(c);
}
}
private static Set<String> buildParentPaths(FileObject fo) {
Set<String> paths = new LinkedHashSet<>();
for (FileObject parent = fo.getParent();
parent != null; parent = parent.getParent()) {
paths.add(parent.getPath());
}
return paths;
}
}
/**
* Adapted from {@code org.netbeans.core.ui.warmup.MenuWarmUpTask}.
*/
class ReheatMenus
implements Runnable {
static void launch() {
EventQueue.invokeLater(new ReheatMenus());
}
private ReheatMenus() {}
private static final RequestProcessor RP = new RequestProcessor(ReheatMenus.class);
private MenuBar bar;
private Component[] comps;
@Override
public void run() {
if (EventQueue.isDispatchThread()) {
if (bar == null) {
Frame main = WindowManager.getDefault().getMainWindow();
assert main != null;
if (main instanceof JFrame) {
JMenuBar menuBar = ((JFrame) main).getJMenuBar();
if (menuBar instanceof MenuBar) {
bar = (MenuBar) menuBar;
RP.post(this);
}
}
} else {
comps = bar.getComponents();
RP.post(this);
}
} else if (comps != null) {
walkMenu(comps);
} else {
bar.waitFinished();
EventQueue.invokeLater(this);
}
}
private void walkMenu(Component[] items) {
for (Component item : items) {
if (!(item instanceof JMenu)) {
continue;
}
try {
Class<?> cls = item.getClass();
//noinspection JavaReflectionMemberAccess
Method m = cls.getDeclaredMethod("doInitialize");
m.setAccessible(true);
m.invoke(item);
walkMenu(((JMenu) item).getMenuComponents());
} catch (Exception x) {
// most likely a NoSuchMethodException on a dynamic submenu
}
}
}
}
/**
* File system which add system hidden folders.
*
* @author Thomas Edwin Santosa
*/
@ServiceProvider(service = FileSystem.class)
public class DynamicLayerContent
extends MultiFileSystem {
private static DynamicLayerContent INSTANCE;
public DynamicLayerContent() {
// will be created on startup, exactly once
INSTANCE = this;
setPropagateMasks(true); // permit *_hidden masks to be used
}
static void clear() {
INSTANCE.setDelegates();
}
static void setHidden(Set<String> paths)
throws IOException {
FileSystem fileSystem = FileUtil.createMemoryFileSystem();
for (String path : paths) {
String pathHidden = path + "_hidden";
String[] pathSplits = pathHidden.split("/");
FileObject fo = fileSystem.getRoot();
for (String folder : pathSplits) {
fo = FileUtil.createFolder(fo, folder);
}
}
INSTANCE.setDelegates(fileSystem);
}
}
/**
* Utility for menu security.
*
* @author Thomas Edwin Santosa
*/
final class MenuVisibilityUtil {
private static final Logger log = LoggerFactory.getLogger(MenuVisibilityUtil.class);
private MenuVisibilityUtil() {
}
static void refresh() {
Runnable run = new Runnable() {
public void run() {
Set<String> hiddenPaths = new HashSet<>();
Frame main = WindowManager.getDefault().getMainWindow();
assert main != null;
if (main instanceof JFrame) {
JFrame frame = (JFrame) main;
JMenuBar menuBar = frame.getJMenuBar();
frame.setJMenuBar(null);
DynamicLayerContent.clear();
try {
DynamicLayerContent.setHidden(MenuFolderUtil.findEmptyFolder());
} catch (IOException e) {
log.error("cannot delete menu {}", hiddenPaths, e);
}
frame.setJMenuBar(menuBar);
ReheatMenus.launch();
}
}
};
if (EventQueue.isDispatchThread()) {
run.run();
} else {
EventQueue.invokeLater(run);
}
}
}
To use this, create an Installer in your plugin and call MenuVisibilityUtil.refresh();
You may want to call MenuVisibilityUtil.refresh()
again if there is any user switch.