| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.click.extras.control; |
| |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.StringTokenizer; |
| |
| import javax.servlet.ServletContext; |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.apache.click.Context; |
| import org.apache.click.control.AbstractControl; |
| import org.apache.click.element.CssImport; |
| import org.apache.click.element.JsImport; |
| import org.apache.click.element.JsScript; |
| import org.apache.click.extras.security.AccessController; |
| import org.apache.click.extras.security.RoleAccessController; |
| import org.apache.click.service.ConfigService; |
| import org.apache.click.util.ClickUtils; |
| import org.apache.click.util.HtmlStringBuffer; |
| import org.apache.commons.lang.StringUtils; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| /** |
| * Provides a hierarchical Menu control. |
| * |
| * <table class='htmlHeader' cellspacing='10'> |
| * <tr> |
| * <td> |
| * <img align='middle' hspace='2'src='menu.png' title='Menu'/> |
| * </td> |
| * </tr> |
| * </table> |
| * |
| * <h3><a name="configuration"></a>Configuration</h3> |
| * |
| * Application menus are normally defined using a configuration file |
| * (<tt>menu.xml</tt> by default) located under the <tt>/WEB-INF</tt> directory |
| * or the root classpath. An example Menu configuration file is provided below. |
| * |
| * <pre class="prettyprint"> |
| * <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| * <menu> |
| * <menu label="Home" path="user/home.htm" roles="tomcat, role1"/> |
| * <menu label="User" path="user/home.htm" roles="tomcat, role1"> |
| * <menu label="User Page 1" path="user/user-1.htm" roles="tomcat, role1"/> |
| * <menu label="User Page 2" path="user/user-2.htm" roles="tomcat, role1"/> |
| * </menu> |
| * <menu label="Admin" path="admin/admin-1.htm" roles="role1"> |
| * <menu label="Admin Page 1" path="admin/admin-1.htm" roles="tomcat, role1"/> |
| * <menu label="Admin Page 2" path="admin/admin-2.htm" roles="tomcat, role1"/> |
| * </menu> |
| * </menu> </pre> |
| * |
| * Use a {@link MenuFactory} to load the Menu items and include the root menu |
| * item in your page: |
| * |
| * <pre class="prettyprint"> |
| * public class BorderPage extends Page { |
| * |
| * private Menu rootMenu; |
| * |
| * public BorderPage() { |
| * MenuFactory menuFactory = new MenuFactory(); |
| * rootMenu = menuFactory.getRootMenu(); |
| * addControl(rootMenu); |
| * } |
| * |
| * @Override |
| * public String getTemplate() { |
| * return "/border-template.htm"; |
| * } |
| * |
| * } </pre> |
| * |
| * <h3><a name="programmatic"></a>Programmatically defined menus</h3> |
| * |
| * It is also possible to create Menus programmatically, for example: |
| * |
| * <pre class="prettyprint"> |
| * public class BorderPage extends Page { |
| * |
| * private static class Menu rootMenu; |
| * |
| * public BorderPage() { |
| * |
| * if (rootMenu == null) { |
| * rootMenu = new MenuBuilder().buildMenu(); |
| * } |
| * |
| * addControl(rootMenu); |
| * } |
| * } </pre> |
| * |
| * <pre class="prettyprint"> |
| * public class MenuBuilder() { |
| * |
| * public Menu buildMenu() { |
| * |
| * Menu rootMenu = new Menu("rootMenu"); |
| * rootMenu.add(createMenu("Home", "home.htm")); |
| * |
| * Menu customerMenu = createMenu("Home", "home.htm"); |
| * rootMenu.add(customerMenu); |
| * |
| * customerMenu.add(createMenu("Search Customers", "search-customers.htm")); |
| * customerMenu.add(createMenu("Edit Customer", "edit-customer.htm")); |
| * |
| * ... |
| * |
| * return rootMenu; |
| * } |
| * |
| * private Menu createMenu(String label, String path) { |
| * Menu menu = new Menu(); |
| * menu.setLabel(label); |
| * menu.setPath(path); |
| * menu.setTitle(label); |
| * return menu; |
| * } |
| * }</pre> |
| * |
| * <h3><a name="rendering"></a>Rendering</h3> |
| * |
| * To render the configured Menu hierarchy you can reference the root menu by |
| * its name in the Velocity template. For example: |
| * <pre class="codeHtml"> |
| * <span class="st">$rootMenu</span> </pre> |
| * |
| * The hierarchical Menu structure is rendered as an HTML list: <ul>. |
| * <p/> |
| * |
| * Alternatively, you can render the menu using a Velocity #macro or Velocity |
| * code in your template. For example: |
| * |
| * <pre class="codeHtml"> |
| * <span class="red">#</span>writeMenu(<span class="st">$rootMenu</span>) </pre> |
| * |
| * An example menu Velocity macro is provided below: |
| * |
| * <pre class="codeHtml"> |
| * <span class="red">#macro</span>( writeMenu <span class="st">$rootMenu</span> ) |
| * |
| * <table id="menuTable" border="0" width="100%" cellspacing="0" cellpadding="0" style="margin-top: 2px;"> |
| * <tr> |
| * <td> |
| * |
| * <div id="searchbar"> |
| * <div class="menustyle" id="menu"> |
| * <ul class="menubar" id="dmenu"> |
| * <span class="red">#foreach</span> (<span class="st">$topMenu</span> <span class="red">in</span> <span class="st">$rootMenu.children</span>) |
| * <span class="red">#if</span> (<span class="st">$topMenu.isUserInRoles</span>() || <span class="st">$topMenu.isUserInChildMenuRoles</span>()) |
| * <span class="red">#if</span> (<span class="st">$topMenu.children.empty</span>) |
| * <li class="topitem"><span class="st">$topMenu</span></li> |
| * <span class="red">#else</span> |
| * <li class="topitem"><span class="st">$topMenu</span> |
| * <ul class="submenu" |
| * <span class="red">#foreach</span> (<span class="st">$subMenu</span> <span class="red">in</span> <span class="st">$topMenu.children</span>) |
| * <span class="red">#if</span> (<span class="st">$subMenu.isUserInRoles</span>()) |
| * ><li><span class="st">$subMenu</span></li |
| * <span class="red">#end</span> |
| * <span class="red">#end</span> |
| * ></ul> |
| * </li> |
| * <span class="red">#end</span> |
| * <span class="red">#end</span> |
| * <span class="red">#end</span> |
| * <span class="red">#if</span> (<span class="st">$request.remoteUser</span>) |
| * <li class="topitem"><a href="<span class="st">$logoutLink.href</span>">Logout</a></li> |
| * <span class="red">#end</span> |
| * </ul> |
| * </div> |
| * </div> |
| * |
| * </td> |
| * </tr> |
| * </table> |
| * |
| * <span class="red">#end</span> </pre> |
| * |
| * This example uses role path based security to only display the menu items |
| * the user is authorized to see. If you are not using this security feature in |
| * your application you should remove the macro {@link #isUserInRoles()} checks so |
| * the menu items will be rendered. |
| * <p/> |
| * Note individual menu items will render themselves as simple anchor tags using |
| * their {@link #toString()} method. For more fine grain control you should |
| * extend your Velocity macro to render individual menu items. |
| * |
| * <h3><a name="security"></a>Security</h3> |
| * |
| * Menus support role based security via the {@link #isUserInRoles()} |
| * method. When creating secure menus define the valid roles in the menu items. |
| * For example: |
| * |
| * <pre class="prettyprint"> |
| * <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| * <menu> |
| * <menu label="Home" path="user/home.htm" roles="user,admin"> |
| * <menu label="Home" path="user/home.htm" roles="user,admin"/> |
| * <menu label="Search" path="user/search.htm" roles="user,admin"/> |
| * </menu> |
| * <menu label="Admin" path="admin/admin.htm"> |
| * <menu label="Home" path="admin/admin.htm" roles="admin"/> |
| * </menu> |
| * </menu> </pre> |
| * |
| * The underlying implementation of isUserInRoles() method is provided by an |
| * {@link AccessController} interface. The default AccessController is provided |
| * by the {@link RoleAccessController} which uses the JEE container is user in |
| * role facility. By providing your own AccessController you can have menu |
| * access control using other security frameworks such as Spring |
| * Security (Acegi) or Apache Shiro. |
| * |
| * <h3><a name="config-dtd"></a>Menu Configuration DTD</h3> |
| * |
| * The Menu config file DTD is provided below: |
| * |
| * <pre class="codeConfig"> |
| * <!-- The Menu (menu.xml) Document Type Definition. --> |
| * <!ELEMENT <span class="red">menu</span> (<span class="st">menu</span>*)> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">id</span> ID #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">name</span> CDATA #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">label</span> CDATA #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">path</span> CDATA #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">target</span> CDATA #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">title</span> CDATA #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">imageSrc</span> CDATA #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">external</span> (true|false) "false"> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">separator</span> (true|false) "false"> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">roles</span> CDATA #IMPLIED> |
| * <!ATTLIST <span class="red">menu</span> <span class="st">pages</span> CDATA #IMPLIED> </pre> |
| * |
| * The Menu DTD is also published online at |
| * <a href="http://click.apache.org/dtds/menu-2.2.dtd">http://click.apache.org/dtds/menu-2.2.dtd</a>. |
| * |
| * <h3><a name="message-resources"></a>Message Resources and Internationalization (i18n)</h3> |
| * |
| * Menus automatically pick up localized messages where applicable. Please see |
| * the following methods on how to customize these messages: |
| * <ul> |
| * <li>{@link #getLabel()}</li> |
| * <li>{@link #getTitle()}</li> |
| * </ul> |
| * |
| * <h3><a name="resources"></a>CSS and JavaScript resources</h3> |
| * |
| * The Menu control makes use of the following resources |
| * (which Click automatically deploys to the application directory, <tt>/click</tt>): |
| * |
| * <ul> |
| * <li><tt>click/menu.css</tt></li> |
| * <li><tt>click/extras-control.js</tt></li> |
| * </ul> |
| * |
| * To import these Menu files simply reference the variables |
| * <span class="blue">$headElements</span> and |
| * <span class="blue">$jsElements</span> in the page template. |
| * |
| * @see org.apache.click.extras.security.AccessController |
| */ |
| public class Menu extends AbstractControl { |
| |
| // Constants -------------------------------------------------------------- |
| |
| private static final long serialVersionUID = 1L; |
| |
| /** |
| * The menu configuration filename: "<tt>/WEB-INF/menu.xml</tt>". |
| */ |
| protected static final String DEFAULT_CONFIG_FILE = "/WEB-INF/menu.xml"; |
| |
| // Class Variables -------------------------------------------------------- |
| |
| /** The cached root Menu as defined in <tt>menu.xml</tt>. */ |
| protected static Menu rootMenu; |
| |
| // Instance Variables ----------------------------------------------------- |
| |
| /** The menu security access controller. */ |
| protected transient AccessController accessController; |
| |
| /** The list of submenu items. */ |
| protected List<Menu> children; |
| |
| /** |
| * The menu path is to an external page flag, by default this value is false. |
| */ |
| protected boolean external; |
| |
| /** |
| * The image src path attribute. If the image src is defined then a |
| * <tt><img/></tt> element will rendered inside the link when |
| * using the Menu {@link #toString()} method. |
| * <p/> |
| * If the image src value is prefixed with '/' then the request context path |
| * will be prefixed to the src value when rendered by the control. |
| */ |
| protected String imageSrc; |
| |
| /** The menu display label. */ |
| protected String label; |
| |
| /** |
| * The list of valid page paths. If any of these page paths match the |
| * current request then the Menu item will be selected. |
| */ |
| protected List<String> pages = new ArrayList<String>(); |
| |
| /** The menu path. */ |
| protected String path; |
| |
| /** The list of valid role names. */ |
| protected List<String> roles; |
| |
| /** The menu separator flag. */ |
| protected boolean separator; |
| |
| /** The target attribute. */ |
| protected String target = ""; |
| |
| /** The tooltip title attribute. */ |
| protected String title; |
| |
| // Constructors ----------------------------------------------------------- |
| |
| /** |
| * Create a new Menu instance. |
| * <p/> |
| * Please ensure you have defined a menu {@link #accessController} if the |
| * menu's {@link #isUserInRoles()} method is going to be called. |
| * |
| * @see #Menu(java.lang.String) |
| */ |
| public Menu() { |
| } |
| |
| /** |
| * Create a new Menu instance with the given name. |
| * <p/> |
| * Please ensure you have defined a menu {@link #accessController} if the |
| * menu's {@link #isUserInRoles()} method is going to be called. For example: |
| * |
| * <pre class="prettyprint"> |
| * public class BorderPage extends Page { |
| * |
| * ... |
| * |
| * public void defineMenus() { |
| * |
| * // Define an accessController |
| * AccessController accessController = new RoleAccessController(); |
| * |
| * // Retrieve some user roles |
| * List roles = securityService.getRoles(); |
| * |
| * Menu menu = new Menu("root"); |
| * menu.setAccessController(accessController); |
| * menu.setRoles(roles); |
| * |
| * Menu subMenu = new Menu("products"); |
| * subMenu.setLabel("Products"); |
| * subMenu.setAccessController(accessController); |
| * subMenu.setRoles(roles); |
| * |
| * menu.add(subMenu); |
| * |
| * ... |
| * } |
| * } </pre> |
| * |
| * @param name the name of the menu |
| */ |
| public Menu(String name) { |
| setName(name); |
| } |
| |
| /** |
| * Create a Menu from the given menu-item XML Element. |
| * |
| * @param menuElement the menu-item XML Element |
| * @param accessController the menu access controller |
| * |
| * @deprecated use |
| * {@link MenuFactory#buildMenu(org.w3c.dom.Element, org.apache.click.extras.security.AccessController, java.lang.Class)} |
| * instead |
| */ |
| @Deprecated |
| protected Menu(Element menuElement, AccessController accessController) { |
| if (menuElement == null) { |
| throw new IllegalArgumentException("Null menuElement parameter"); |
| } |
| if (accessController == null) { |
| throw new IllegalArgumentException("Null accessController parameter"); |
| } |
| |
| setAccessController(accessController); |
| |
| String nameAtr = menuElement.getAttribute("name"); |
| if (StringUtils.isNotBlank(nameAtr)) { |
| setName(nameAtr); |
| } |
| |
| String labelAtr = menuElement.getAttribute("label"); |
| if (StringUtils.isNotBlank(labelAtr)) { |
| setLabel(labelAtr); |
| } |
| |
| String imageSrcAtr = menuElement.getAttribute("imageSrc"); |
| if (StringUtils.isNotBlank(imageSrcAtr)) { |
| setImageSrc(imageSrcAtr); |
| } |
| |
| String pathAtr = menuElement.getAttribute("path"); |
| if (StringUtils.isNotBlank(pathAtr)) { |
| setPath(pathAtr); |
| } |
| |
| String titleAtr = menuElement.getAttribute("title"); |
| if (StringUtils.isNotBlank(titleAtr)) { |
| setTitle(titleAtr); |
| } |
| |
| String targetAtr = menuElement.getAttribute("target"); |
| if (StringUtils.isNotBlank(targetAtr)) { |
| setTarget(targetAtr); |
| } |
| |
| String externalAtr = menuElement.getAttribute("external"); |
| if ("true".equalsIgnoreCase(externalAtr)) { |
| setExternal(true); |
| } |
| |
| String separatorAtr = menuElement.getAttribute("separator"); |
| if ("true".equalsIgnoreCase(separatorAtr)) { |
| setSeparator(true); |
| } |
| |
| String pagesValue = menuElement.getAttribute("pages"); |
| if (StringUtils.isNotBlank(pagesValue)) { |
| StringTokenizer tokenizer = new StringTokenizer(pagesValue, ","); |
| while (tokenizer.hasMoreTokens()) { |
| String path = tokenizer.nextToken().trim(); |
| path = (path.startsWith("/")) ? path : "/" + path; |
| getPages().add(path); |
| } |
| } |
| |
| String rolesValue = menuElement.getAttribute("roles"); |
| if (StringUtils.isNotBlank(rolesValue)) { |
| StringTokenizer tokenizer = new StringTokenizer(rolesValue, ","); |
| while (tokenizer.hasMoreTokens()) { |
| getRoles().add(tokenizer.nextToken().trim()); |
| } |
| } |
| |
| NodeList childElements = menuElement.getChildNodes(); |
| for (int i = 0, size = childElements.getLength(); i < size; i++) { |
| Node node = childElements.item(i); |
| if (node instanceof Element) { |
| Menu childMenu = new Menu((Element) node, accessController); |
| add(childMenu); |
| } |
| } |
| } |
| |
| // Constructor Methods ---------------------------------------------------- |
| |
| /** |
| * Return root menu item defined in the WEB-INF/menu.xml or classpath |
| * menu.xml, and which uses JEE Role Based Access Control (RoleAccessController). |
| * |
| * @see RoleAccessController |
| * |
| * @deprecated use {@link MenuFactory#getRootMenu()} instead |
| * |
| * @return the root menu item defined in the WEB-INF/menu.xml file or menu.xml |
| * in the root classpath |
| */ |
| @Deprecated |
| public static Menu getRootMenu() { |
| return getRootMenu(new RoleAccessController()); |
| } |
| |
| /** |
| * Return root menu item defined in the WEB-INF/menu.xml or classpath |
| * menu.xml, and which uses the provided AccessController. |
| * |
| * @deprecated use |
| * {@link MenuFactory#getRootMenu(org.apache.click.extras.security.AccessController)} |
| * instead |
| * |
| * @param accessController the menu access controller |
| * @return the root menu item defined in the WEB-INF/menu.xml file or menu.xml |
| * in the root classpath |
| */ |
| @Deprecated |
| public static Menu getRootMenu(AccessController accessController) { |
| if (accessController == null) { |
| throw new IllegalArgumentException("Null accessController parameter"); |
| } |
| |
| // If menu is cached return it |
| if (rootMenu != null) { |
| return rootMenu; |
| } |
| |
| Menu loadedMenu = loadRootMenu(accessController); |
| |
| ServletContext servletContext = Context.getThreadLocalContext().getServletContext(); |
| ConfigService configService = ClickUtils.getConfigService(servletContext); |
| |
| if (configService.isProductionMode() || configService.isProfileMode()) { |
| // Cache menu in production modes |
| rootMenu = loadedMenu; |
| } |
| |
| return loadedMenu; |
| } |
| |
| // Public Attributes ------------------------------------------------------ |
| |
| /** |
| * Return the menu access controller. |
| * |
| * @return the menu access controller |
| */ |
| public AccessController getAccessController() { |
| return accessController; |
| } |
| |
| /** |
| * Set the menu access controller. |
| * |
| * @param accessController the menu access controller |
| */ |
| public void setAccessController(AccessController accessController) { |
| this.accessController = accessController; |
| } |
| |
| /** |
| * Return true if the menu contains any child submenus. |
| * |
| * @return true if the menu contains any child submenus |
| */ |
| public boolean hasChildren() { |
| if (children == null || children.isEmpty()) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Return list of of submenu items. |
| * |
| * @return the list of submenu items |
| */ |
| public List<Menu> getChildren() { |
| if (children == null) { |
| children = new ArrayList<Menu>(); |
| } |
| return children; |
| } |
| |
| /** |
| * Return true if the menu path refers to an external resource. |
| * |
| * @return true if the menu path refers to an external resource |
| */ |
| public boolean isExternal() { |
| return external; |
| } |
| |
| /** |
| * Set whether the menu path refers to an external resource. |
| * |
| * @param value the flag as to whether the menu path refers to an external resource |
| */ |
| public void setExternal(boolean value) { |
| external = value; |
| } |
| |
| /** |
| * Return the image src path attribute. If the image src is defined then a |
| * <tt><img/></tt> element will rendered inside the link when |
| * using the Menu {@link #toString()} method. |
| * <p/> |
| * If the src value is prefixed with '/' then the request context path will |
| * be prefixed to the src value when rendered by the control. |
| * |
| * @return the image src path attribute |
| */ |
| public String getImageSrc() { |
| return imageSrc; |
| } |
| |
| /** |
| * Set the image src path attribute. If the src value is prefixed with |
| * '/' then the request context path will be prefixed to the src value when |
| * rendered by the control. |
| * |
| * @param src the image src path attribute |
| */ |
| public void setImageSrc(String src) { |
| this.imageSrc = src; |
| } |
| |
| /** |
| * Return the menu item display label. |
| * <p/> |
| * If the label value is null, this method will attempt to find a |
| * localized label message in the parent messages of the root menu using the |
| * key: |
| * |
| * <blockquote> |
| * <tt>getName() + ".label"</tt> |
| * </blockquote> |
| * |
| * If not found then the message will be looked up in the |
| * <tt>/click-control.properties</tt> file using the same key. |
| * If a value is still not found, the Menu name will be converted |
| * into a label using the method: {@link ClickUtils#toLabel(String)} |
| * <p/> |
| * For example given the properties file <tt>src/click-page.properties</tt>: |
| * |
| * <pre class="codeConfig"> |
| * <span class="st">customers</span>.label=<span class="red">Customers</span> |
| * <span class="st">customers</span>.title=<span class="red">Find a specific customer</span> </pre> |
| * |
| * The menu.xml (<b>note</b> that no label attribute is present): |
| * <pre class="prettyprint"> |
| * <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| * <menu> |
| * <menu name="customers" path="customers.htm" roles="view-customers"/> |
| * |
| * ... |
| * </menu> </pre> |
| * |
| * Will render the Menu label and title properties as: |
| * |
| * <pre class="codeHtml"> |
| * <li><a title="<span class="red">Find a specific customer</span>" ... ><span class="red">Customers</span></a></li> </pre> |
| * |
| * When a label value is not set, or defined in any properties files, then |
| * its value will be created from the Menu name. |
| * <p/> |
| * For example given the <tt>menu.xml</tt> file: |
| * |
| * <pre class="prettyprint"> |
| * <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| * <menu> |
| * <menu name="product" path="product.htm" roles="view-product"/> |
| * |
| * ... |
| * </menu> </pre> |
| * |
| * Will render the Menu label as: |
| * |
| * <pre class="codeHtml"> |
| * <li><a ... ><span class="red">Product</span></a></li> </pre> |
| * |
| * @return the display label of the Menu item |
| */ |
| public String getLabel() { |
| // Return cached label, if set |
| if (label != null) { |
| return label; |
| } |
| |
| String localName = getName(); |
| |
| if (localName != null) { |
| Menu root = findRootMenu(); |
| |
| // Use root menu messages to lookup the label |
| String i18nLabel = root.getMessage(localName + ".label"); |
| |
| if (i18nLabel == null) { |
| i18nLabel = ClickUtils.toLabel(localName); |
| } |
| |
| // NOTE: don't cache the i18nLabel, since menus are often cached |
| // statically |
| return i18nLabel; |
| } |
| return null; |
| } |
| |
| /** |
| * Set the label of the Menu item. |
| * |
| * @param label the label of the Menu item |
| */ |
| public void setLabel(String label) { |
| this.label = label; |
| } |
| |
| /** |
| * Return the list of valid Page paths for the Menu item. If any of these |
| * page paths match the current request then the Menu item will be selected. |
| * |
| * @return the list of valid Page paths |
| */ |
| public List<String> getPages() { |
| return pages; |
| } |
| |
| /** |
| * Set the list of valid Page paths. If any of these page paths match the |
| * current request then the Menu item will be selected. |
| * |
| * @param pages the list of valid Page paths |
| */ |
| public void setPages(List<String> pages) { |
| this.pages = pages; |
| } |
| |
| /** |
| * Return the path of the Menu item. |
| * |
| * @return the path of the Menu item |
| */ |
| public String getPath() { |
| return path; |
| } |
| |
| /** |
| * Set the path of the Menu item. |
| * |
| * @param path the path of the Menu item |
| */ |
| public void setPath(String path) { |
| this.path = path; |
| } |
| |
| /** |
| * Return true if the menu has roles defined, false otherwise. |
| * |
| * @return true if the menu has roles defined, false otherwise |
| */ |
| public boolean hasRoles() { |
| return roles != null && !roles.isEmpty(); |
| } |
| |
| /** |
| * Return the list of roles for the Menu item. |
| * |
| * @return the list of roles for the Menu item |
| */ |
| public List<String> getRoles() { |
| if (roles == null) { |
| roles = new ArrayList<String>(); |
| } |
| return roles; |
| } |
| |
| /** |
| * Set the list of valid roles for the Menu item. |
| * |
| * @param roles the list of valid roles for the Menu item |
| */ |
| public void setRoles(List<String> roles) { |
| this.roles = roles; |
| } |
| |
| /** |
| * Return true if the Menu item is selected. |
| * |
| * @return true if the Menu item is selected |
| */ |
| public boolean isSelected() { |
| if (this == rootMenu) { |
| return true; |
| } |
| |
| final String pageToView = getContext().getResourcePath(); |
| |
| boolean selected = false; |
| |
| if (getPages().contains(pageToView)) { |
| selected = true; |
| |
| } else { |
| String localPath = getPath(); |
| if (localPath != null) { |
| localPath = localPath.startsWith("/") ? localPath : "/" + localPath; |
| selected = localPath.equals(pageToView); |
| } else { |
| selected = false; |
| } |
| } |
| |
| for (int i = 0, size = getChildren().size(); i < size; i++) { |
| Menu menu = getChildren().get(i); |
| if (menu.isSelected()) { |
| selected = true; |
| } |
| } |
| |
| return selected; |
| } |
| |
| /** |
| * Return the selected child menu, or null if no child menu is selected. |
| * |
| * @return the selected child menu |
| */ |
| public Menu getSelectedChild() { |
| if (isSelected()) { |
| for (int i = 0, size = getChildren().size(); i < size; i++) { |
| Menu menu = getChildren().get(i); |
| if (menu.isSelected()) { |
| return menu; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return true if the Menu item is a separator. |
| * |
| * @return true if the Menu item is a separator |
| */ |
| public boolean isSeparator() { |
| return separator; |
| } |
| |
| /** |
| * Set whether the Menu item is a separator. |
| * |
| * @param separator the flag indicating whether the Menu item is a separator |
| */ |
| public void setSeparator(boolean separator) { |
| this.separator = separator; |
| } |
| |
| /** |
| * Return true if the user is in one of the menu roles, or if any child |
| * menus have the user in one of their menu roles. Otherwise the method will |
| * return false. |
| * <p/> |
| * This method internally uses the |
| * {@link org.apache.click.extras.security.AccessController#hasAccess(javax.servlet.http.HttpServletRequest, java.lang.String) AccessController#hasAccess(HttpServletRequest request, String roleName)} |
| * method where the rolenames are derived from the {@link #getRoles()} property. |
| * <p/> |
| * If no {@link #getRoles() roles} are defined, the AccessController are invoked |
| * with a <tt>null</tt> argument to determine whether access is permitted to |
| * menus without roles. |
| * |
| * @return true if the user is in one of the menu roles, or false otherwise |
| * @throws IllegalStateException if the menu accessController is not defined |
| */ |
| public boolean isUserInRoles() { |
| if (getAccessController() == null) { |
| String msg = "Menu accessController has not been defined"; |
| throw new IllegalStateException(msg); |
| } |
| |
| HttpServletRequest request = getContext().getRequest(); |
| |
| if (hasRoles()) { |
| for (int i = 0, size = getRoles().size(); i < size; i++) { |
| String rolename = getRoles().get(i); |
| if (getAccessController().hasAccess(request, rolename)) { |
| return true; |
| } |
| } |
| } else { |
| // Check access for menus without roles. CLK-724 |
| return getAccessController().hasAccess(request, null); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Return true if any child menus have the user in one of their menu roles. |
| * Otherwise the method will return false. |
| * <p/> |
| * This method internally uses the <tt>HttpServletRequest</tt> function <tt>isUserInRole(rolename)</tt>, |
| * where the rolenames are derived from the {@link #getRoles()} property. |
| * |
| * @return true if the user is in one of the child menu roles, or false otherwise |
| */ |
| public boolean isUserInChildMenuRoles() { |
| for (int i = 0, size = getChildren().size(); i < size; i++) { |
| Menu child = getChildren().get(i); |
| if (child.isUserInRoles()) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Return the target attribute of the Menu item. |
| * |
| * @return the target attribute of the Menu item |
| */ |
| public String getTarget() { |
| return target; |
| } |
| |
| /** |
| * Set the target attribute of the Menu item. |
| * |
| * @param target the target attribute of the Menu item |
| */ |
| public void setTarget(String target) { |
| this.target = target; |
| } |
| |
| /** |
| * Return the 'title' attribute of the Menu item, or null if not defined. |
| * <p/> |
| * If the title value is null, this method will attempt to find a |
| * localized title message in the parent messages of the root menu using the |
| * key: |
| * |
| * <blockquote> |
| * <tt>getName() + ".title"</tt> |
| * </blockquote> |
| * |
| * If not found then the message will be looked up in the |
| * <tt>/click-control.properties</tt> file using the same key. If still |
| * not found the title will be left as null and will not be rendered. |
| * <p/> |
| * For example given the properties file <tt>src/click-page.properties</tt>: |
| * |
| * <pre class="codeConfig"> |
| * <span class="st">customers</span>.label=<span class="red">Customers</span> |
| * <span class="st">customers</span>.title=<span class="red">Find a specific customer</span> </pre> |
| * |
| * The menu.xml (<b>note</b> that no title attribute is present): |
| * <pre class="prettyprint"> |
| * <?xml version="1.0" encoding="UTF-8" standalone="yes"?> |
| * <menu> |
| * <menu name="customers" path="customers.htm" roles="view-customers"/> |
| * |
| * ... |
| * </menu> </pre> |
| * |
| * Will render the Menu label and title properties as: |
| * |
| * <pre class="codeHtml"> |
| * <li><a title="<span class="red">Find a specific customer</span>" ... ><span class="red">Customers</span></a></li> </pre> |
| * |
| * @return the 'title' attribute of the Menu item |
| */ |
| public String getTitle() { |
| // Return cached title if set |
| if (title != null) { |
| return title; |
| } |
| |
| String localName = getName(); |
| |
| if (localName != null) { |
| // Use root menu messages to lookup the title |
| Menu root = findRootMenu(); |
| |
| // NOTE: don't cache the i18nTitle, since menus are often cached |
| // statically |
| return root.getMessage(localName + ".title"); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Set the title attribute of the Menu item. |
| * |
| * @param title the title attribute of the Menu item |
| */ |
| public void setTitle(String title) { |
| this.title = title; |
| } |
| |
| /** |
| * Return the menu anchor HREF attribute. If the menu is referring |
| * to an external path, this method will simply return the path, |
| * otherwise it will return the menu path prefixed with the |
| * request context path. |
| * <p/> |
| * If the path refers to a hash "#" symbol, this method will return |
| * a "#". It is useful to assign a "#" to the path of a menu item |
| * containing children, because most modern browsers will not submit |
| * the page if clicked on. |
| * |
| * @return the menu anchor HREF attribute |
| */ |
| public String getHref() { |
| String localPath = getPath(); |
| if (isExternal()) { |
| return localPath; |
| } |
| |
| if ("#".equals(localPath)) { |
| return getContext().getResponse().encodeURL(localPath); |
| |
| } else { |
| Context context = getContext(); |
| if (localPath == null) { |
| // Guard against rendering "null" in the href |
| localPath = ""; |
| } |
| StringBuilder sb = new StringBuilder(); |
| String contextPath = context.getRequest().getContextPath(); |
| sb.append(contextPath); |
| if (localPath.length() > 0 && localPath.charAt(0) != '/') { |
| sb.append('/'); |
| } |
| sb.append(localPath); |
| return context.getResponse().encodeURL(sb.toString()); |
| } |
| } |
| |
| /** |
| * Return the Menu HEAD elements to be included in the page. |
| * The following resources are returned: |
| * |
| * <ul> |
| * <li><tt>click/menu.css</tt></li> |
| * <li><tt>click/control.js</tt></li> |
| * <li><tt>click/menu-fix-ie6.js</tt> (fixes IE6 menu burnthrough and hover issues)</li> |
| * </ul> |
| * |
| * @see org.apache.click.Control#getHeadElements() |
| * |
| * @return the HTML HEAD elements for the control |
| */ |
| @Override |
| public List<org.apache.click.element.Element> getHeadElements() { |
| String id = getId(); |
| if (id == null) { |
| throw new IllegalStateException("Menu name is not set"); |
| } |
| |
| if (headElements == null) { |
| headElements = super.getHeadElements(); |
| |
| Context context = getContext(); |
| String versionIndicator = ClickUtils.getResourceVersionIndicator(context); |
| |
| CssImport cssImport = new CssImport("/click/menu.css", versionIndicator); |
| headElements.add(cssImport); |
| |
| JsImport jsImport = new JsImport("/click/control.js", versionIndicator); |
| headElements.add(jsImport); |
| |
| jsImport = new JsImport("/click/menu-fix-ie6.js", versionIndicator); |
| jsImport.setConditionalComment(JsImport.IF_LESS_THAN_IE7); |
| headElements.add(jsImport); |
| |
| JsScript script = new JsScript(); |
| script.setId(id + "-js-setup"); |
| |
| // Script must be executed as soon as browser dom is ready |
| script.setExecuteOnDomReady(true); |
| script.setConditionalComment(JsImport.IF_LESS_THAN_IE7); |
| |
| HtmlStringBuffer buffer = new HtmlStringBuffer(); |
| buffer.append(" if(typeof Click != 'undefined' && typeof Click.menu != 'undefined') {\n"); |
| buffer.append(" if(typeof Click.menu.fixHiddenMenu != 'undefined') {\n"); |
| buffer.append(" Click.menu.fixHiddenMenu(\"").append(id).append("\");\n"); |
| buffer.append(" Click.menu.fixHover(\"").append(id).append("\");\n"); |
| buffer.append(" }\n"); |
| buffer.append(" }\n"); |
| script.setContent(buffer.toString()); |
| headElements.add(script); |
| } |
| |
| return headElements; |
| } |
| |
| // Public Methods --------------------------------------------------------- |
| |
| /** |
| * Add the given menu as a submenu. The menu will also be set as the parent |
| * of the submenu. |
| * |
| * @param menu the submenu to add |
| * @return the menu that was added |
| */ |
| public Menu add(Menu menu) { |
| getChildren().add(menu); |
| menu.setParent(this); |
| return menu; |
| } |
| |
| /** |
| * Return true if this menu contains the given menu, false otherwise. |
| * <p/> |
| * To test if the given menu is contained, this method will test against |
| * both the menu object reference as well as the menu name. |
| * |
| * @return true if this menu contains the given menu, false otherwise |
| */ |
| public boolean contains(Menu menu) { |
| if (hasChildren()) { |
| for (Menu child : getChildren()) { |
| |
| // Test against object reference |
| if (child == menu) { |
| return true; |
| } |
| |
| // Test against menu name |
| String childName = child.getName(); |
| String menuName = menu.getName(); |
| if (childName != null && menuName != null) { |
| if (childName.equals(menuName)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Find the root menu, or null if no root menu can be found. |
| * |
| * @return the root menu, or null if no root menu can be found. |
| */ |
| public Menu findRootMenu() { |
| Menu root = this; |
| Object parentMenu = root.getParent(); |
| while (parentMenu instanceof Menu) { |
| root = (Menu) parentMenu; |
| parentMenu = root.getParent(); |
| } |
| return root; |
| } |
| |
| /** |
| * Return true if this is the root menu, false otherwise. |
| * |
| * @return true if this menu is the root menu, false otherwise |
| */ |
| public boolean isRoot() { |
| return !(getParent() instanceof Menu); |
| } |
| |
| /** |
| * This sets the parent to be null. |
| * |
| * @see org.apache.click.Control#onDestroy() |
| */ |
| @Override |
| public void onDestroy() { |
| setParent(null); |
| } |
| |
| /** |
| * Render an HTML representation of the Menu. |
| * <p/> |
| * If <tt>this</tt> menu instance is the root menu |
| * ({@link #isRoot()} returns true), the menu and all its submenus |
| * (recursively), will be rendered by delegating rendering to the method |
| * {@link #renderRootMenu(org.apache.click.util.HtmlStringBuffer) renderRootMenu}. |
| * The menu structure will be rendered as an HTML List consisting of <ul> |
| * and <li> elements. |
| * <p/> |
| * If <tt>this</tt> menu instance is <tt>not</tt> the root menu, this menu |
| * will be rendered by delegating rendering to the method |
| * {@link #renderMenuLink(org.apache.click.util.HtmlStringBuffer, org.apache.click.extras.control.Menu)}. |
| * The menu will be rendered as a link: <a>. |
| * <p/> |
| * By having two render modes one can render the entire menu |
| * automatically, or render each menu item manually using a Velocity macro. |
| * |
| * @see #toString() |
| * |
| * @param buffer the specified buffer to render the control's output to |
| */ |
| @Override |
| public void render(HtmlStringBuffer buffer) { |
| if (isRoot()) { |
| |
| renderRootMenu(buffer); |
| } else { |
| |
| if (isSeparator()) { |
| renderSeparator(buffer, this); |
| } else { |
| renderMenuLink(buffer, this); |
| } |
| } |
| } |
| |
| /** |
| * Return an HTML representation of the menu. |
| * |
| * @see #render(org.apache.click.util.HtmlStringBuffer) |
| * |
| * @return an HTML anchor tag representation of the menu |
| */ |
| @Override |
| public String toString() { |
| HtmlStringBuffer buffer = new HtmlStringBuffer(); |
| render(buffer); |
| return buffer.toString(); |
| } |
| |
| // Protected Methods ------------------------------------------------------ |
| |
| /** |
| * Render an HTML representation of the root menu. |
| * |
| * @param buffer the buffer to render to |
| */ |
| protected void renderRootMenu(HtmlStringBuffer buffer) { |
| buffer.elementStart("div"); |
| buffer.appendAttribute("id", getId()); |
| buffer.appendAttribute("class", "menustyle"); |
| buffer.closeTag(); |
| buffer.append("\n"); |
| |
| int depth = 0; |
| renderMenuList(buffer, this, depth); |
| buffer.elementEnd("div"); |
| } |
| |
| /** |
| * Render an html representation of the menu list (<ul>) structure. |
| * <p/> |
| * <b>Please note</b>: the method |
| * {@link #canRender(org.apache.click.extras.control.Menu, int) canRender(menu)} |
| * controls whether menu items are rendered or not. If <tt>canRender</tt> |
| * returns true, the menu item is rendered, otherwise it is skipped. |
| * |
| * @see #canRender(org.apache.click.extras.control.Menu, int) |
| * |
| * @param buffer the buffer to render to |
| * @param menu the menu that is currently rendered |
| * @param depth the current depth in the menu hierarchy |
| */ |
| protected void renderMenuList(HtmlStringBuffer buffer, Menu menu, int depth) { |
| buffer.elementStart("ul"); |
| renderMenuListAttributes(buffer, menu, depth); |
| buffer.closeTag(); |
| buffer.append("\n"); |
| |
| for (Menu child : menu.getChildren()) { |
| |
| if (canRender(child, depth)) { |
| |
| buffer.elementStart("li"); |
| renderMenuListItemAttributes(buffer, child, depth); |
| buffer.closeTag(); |
| |
| if (child.isSeparator()) { |
| renderSeparator(buffer, child); |
| } else { |
| renderMenuLink(buffer, child); |
| } |
| |
| if (child.hasChildren()) { |
| buffer.append("\n"); |
| renderMenuList(buffer, child, depth + 1); |
| } |
| buffer.elementEnd("li"); |
| buffer.append("\n"); |
| } |
| } |
| |
| buffer.elementEnd("ul"); |
| buffer.append("\n"); |
| } |
| |
| /** |
| * Return true if the given menu can be rendered, false otherwise. |
| * <p/> |
| * If the menu {@link #hasRoles() has roles} defined, this method will return |
| * true if the user is in one of the menu roles, false otherwise. This method |
| * delegates to {@link #isUserInRoles()} if the menu has roles defined. |
| * <p/> |
| * If the menu has no roles defined, this method returns true. |
| * |
| * @param menu the menu that should be rendered or not |
| * @param depth the current depth in the menu hierarchy |
| * @return true if the menu can be rendered, false otherwise |
| */ |
| protected boolean canRender(Menu menu, int depth) { |
| // TODO add and check visible property |
| return menu.isUserInRoles(); |
| } |
| |
| /** |
| * Render the attributes of the menu list (>ul<). |
| * |
| * @param buffer the buffer to render to |
| * @param menu the menu being rendered |
| * @param depth the current depth in the menu hierarchy |
| */ |
| protected void renderMenuListAttributes(HtmlStringBuffer buffer, Menu menu, |
| int depth) { |
| |
| if (depth == 0) { |
| buffer.appendAttribute("class", "menubar"); |
| } else { |
| buffer.appendAttribute("class", "submenu"); |
| } |
| } |
| |
| /** |
| * Render the attributes of the menu list item (>li<). |
| * |
| * @param buffer the buffer to render to |
| * @param menu the menu being rendered |
| * @param depth the current depth in the menu hierarchy |
| */ |
| protected void renderMenuListItemAttributes(HtmlStringBuffer buffer, Menu menu, |
| int depth) { |
| |
| if (depth == 0) { |
| buffer.append(" class=\"menuitem topitem"); |
| } else { |
| buffer.append(" class=\"menuitem"); |
| } |
| if (menu.hasChildren()) { |
| buffer.append(" has-submenu"); |
| } |
| buffer.append("\""); |
| } |
| |
| /** |
| * Render an HTML link (<a>) representation of the given menu. |
| * <p/> |
| * If the menu item is selected the anchor tag will be rendered with |
| * class="selected" attribute. |
| * |
| * @param buffer the buffer to render to |
| * @param menu the menu to render |
| */ |
| protected void renderMenuLink(HtmlStringBuffer buffer, Menu menu) { |
| buffer.elementStart("a"); |
| |
| String id = menu.getAttribute("id"); |
| if (id != null) { |
| buffer.appendAttribute("id", id); |
| } |
| |
| if (menu.getName() != null) { |
| buffer.appendAttribute("name", menu.getName()); |
| } |
| |
| menu.renderMenuHref(buffer); |
| |
| if (menu.getTarget() != null && menu.getTarget().length() > 0) { |
| buffer.appendAttribute("target", menu.getTarget()); |
| } |
| |
| String menuTitle = menu.getTitle(); |
| if (menuTitle != null && menuTitle.length() > 0) { |
| buffer.appendAttributeEscaped("title", menuTitle); |
| } |
| |
| if (menu.isSelected()) { |
| buffer.appendAttribute("class", "selected"); |
| } |
| |
| // TODO need to re-add visible and enabled properties |
| if (menu.hasAttributes()) { |
| buffer.appendAttributes(menu.getAttributes()); |
| } |
| |
| buffer.closeTag(); |
| |
| String menuLabel = menu.getLabel(); |
| |
| if (StringUtils.isNotBlank(menu.getImageSrc())) { |
| buffer.elementStart("img"); |
| buffer.appendAttribute("border", "0"); |
| buffer.appendAttribute("class", "link"); |
| |
| if (menuTitle != null) { |
| buffer.appendAttributeEscaped("alt", menuTitle); |
| } else { |
| buffer.appendAttributeEscaped("alt", menuLabel); |
| } |
| |
| String src = menu.getImageSrc(); |
| if (StringUtils.isNotBlank(src)) { |
| if (src.charAt(0) == '/') { |
| src = getContext().getRequest().getContextPath() + src; |
| } |
| buffer.appendAttribute("src", src); |
| } |
| |
| buffer.elementEnd(); |
| |
| if (menuLabel != null) { |
| buffer.append(menuLabel); |
| } |
| |
| } else { |
| if (menuLabel != null) { |
| buffer.append(menuLabel); |
| } |
| } |
| |
| buffer.elementEnd("a"); |
| } |
| |
| /** |
| * Render an HTML representation of the menu as a separator. |
| * |
| * @param buffer the buffer to render to |
| * @param menu the menu to render as a separator |
| */ |
| protected void renderSeparator(HtmlStringBuffer buffer, Menu menu) { |
| buffer.append("<hr/>"); |
| } |
| |
| /** |
| * Render the menu <tt>"href"</tt> attribute. This method can be overridden |
| * to render dynamic <tt>"href"</tt> parameters, for example: |
| * |
| * <pre class="prettyprint"> |
| * public class MyPage extends BorderPage { |
| * |
| * public MyPage() { |
| * Menu rootMenu = new MenuFactory().getRootMenu(); |
| * |
| * final String contextPath = getContext().getRequest().getContextPath(); |
| * |
| * Menu menu = new Menu() { |
| * @Override |
| * protected void renderMenuHref(HtmlStringBuffer buffer) { |
| * buffer.appendAttribute("href", contextPath + "/my-page.htm?customer=" + getCustomerId()); |
| * } |
| * }); |
| * |
| * menu.setName("customer"); |
| * menu.setLabel("Customer Lookup"); |
| * |
| * // Guard against adding child menu more than once |
| * if (!rootMenu.contains(menu)) { |
| * rootMenu.add(menu); |
| * } |
| * } |
| * } </pre> |
| * |
| * @param buffer the buffer to render the href attribute to |
| */ |
| protected void renderMenuHref(HtmlStringBuffer buffer) { |
| String href = getHref(); |
| buffer.appendAttribute("href", href); |
| |
| if ("#".equals(href)) { |
| // If hyperlink does not return false, clicking on it will |
| // scroll to the top of the page. |
| buffer.appendAttribute("onclick", "return false;"); |
| } |
| } |
| |
| /** |
| * Return a copy of the Applications root Menu as defined in the |
| * configuration file "<tt>/WEB-INF/menu.xml</tt>", with the Control |
| * name <tt>"rootMenu"</tt>. |
| * <p/> |
| * The returned root menu is always selected. |
| * |
| * @deprecated use |
| * {@link MenuFactory#loadFromMenuXml(java.lang.String, java.lang.String, org.apache.click.extras.security.AccessController, java.lang.Class)} |
| * instead |
| * |
| * @param accessController the menu access controller |
| * @return a copy of the application's root Menu |
| */ |
| @Deprecated |
| protected static Menu loadRootMenu(AccessController accessController) { |
| if (accessController == null) { |
| throw new IllegalArgumentException("Null accessController parameter"); |
| } |
| |
| Context context = Context.getThreadLocalContext(); |
| |
| Menu menu = new Menu("rootMenu"); |
| menu.setAccessController(accessController); |
| |
| ServletContext servletContext = context.getServletContext(); |
| InputStream inputStream = |
| servletContext.getResourceAsStream(DEFAULT_CONFIG_FILE); |
| |
| if (inputStream == null) { |
| inputStream = ClickUtils.getResourceAsStream("/menu.xml", Menu.class); |
| if (inputStream == null) { |
| String msg = |
| "could not find configuration file:" + DEFAULT_CONFIG_FILE |
| + " or menu.xml on classpath"; |
| throw new RuntimeException(msg); |
| } |
| } |
| |
| Document document = ClickUtils.buildDocument(inputStream); |
| |
| Element rootElm = document.getDocumentElement(); |
| |
| NodeList list = rootElm.getChildNodes(); |
| |
| for (int i = 0; i < list.getLength(); i++) { |
| Node node = list.item(i); |
| if (node instanceof Element) { |
| Menu childMenu = new Menu((Element) node, accessController); |
| menu.add(childMenu); |
| } |
| } |
| |
| return menu; |
| } |
| |
| } |