1
2
3
4
5
6 package net.sf.navigator.menu;
7
8 import net.sf.navigator.displayer.MenuDisplayerMapping;
9 import net.sf.navigator.displayer.VelocityMenuDisplayer;
10 import net.sf.navigator.util.LoadableResource;
11 import net.sf.navigator.util.LoadableResourceException;
12 import org.apache.commons.collections.map.LinkedMap;
13 import org.apache.commons.digester.Digester;
14 import org.apache.commons.logging.Log;
15 import org.apache.commons.logging.LogFactory;
16
17 import javax.servlet.ServletContext;
18 import java.io.InputStream;
19 import java.io.Serializable;
20 import java.util.*;
21
22 /**
23 * Holder of Menus and their items. Can be populated programmatically.
24 *
25 * @author ssayles, mraible
26 */
27 public class MenuRepository implements LoadableResource, Serializable {
28
29
30 public static final String MENU_REPOSITORY_KEY = "net.sf.navigator.menu.MENU_REPOSITORY";
31 private static Log log = LogFactory.getLog(MenuRepository.class);
32
33
34
35 protected String config = null;
36 protected String name = null;
37 protected ServletContext servletContext = null;
38 protected LinkedMap menus = new LinkedMap();
39 protected LinkedMap displayers = new LinkedMap();
40 protected LinkedMap templates = new LinkedMap();
41 private String breadCrumbDelimiter;
42
43
44 public Set getMenuNames() {
45 return menus.keySet();
46 }
47
48 /**
49 * Convenience method for dynamic menus - returns the top-level menus
50 * only.
51 */
52 public List getTopMenus() {
53 List topMenus = new ArrayList();
54 if (menus == null) {
55 log.warn("No menus found in repository!");
56 return topMenus;
57 }
58
59 for (Iterator it = menus.keySet().iterator(); it.hasNext();) {
60 String name = (String) it.next();
61 MenuComponent menu = getMenu(name);
62 if (menu.getParent() == null) {
63 topMenus.add(menu);
64 }
65 }
66 return topMenus;
67 }
68
69 public MenuComponent getMenu(String menuName) {
70 return (MenuComponent) menus.get(menuName);
71 }
72
73 public MenuDisplayerMapping getMenuDisplayerMapping(String displayerName) {
74 return (MenuDisplayerMapping) displayers.get(displayerName);
75 }
76
77 protected Digester initDigester() {
78 Digester digester = new Digester();
79 digester.setClassLoader(Thread.currentThread().getContextClassLoader());
80 digester.push(this);
81
82
83
84 digester.addObjectCreate("MenuConfig/Menus/Menu", "net.sf.navigator.menu.MenuComponent", "type");
85 digester.addSetProperties("MenuConfig/Menus/Menu");
86 digester.addSetNext("MenuConfig/Menus/Menu", "addMenu");
87
88
89 digester.addObjectCreate("MenuConfig/Menus/Menu/Item", "net.sf.navigator.menu.MenuComponent", "type");
90 digester.addSetProperties("MenuConfig/Menus/Menu/Item");
91 digester.addSetNext("MenuConfig/Menus/Menu/Item", "addMenuComponent", "net.sf.navigator.menu.MenuComponent");
92
93
94 digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item", "net.sf.navigator.menu.MenuComponent", "type");
95 digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item");
96 digester.addSetNext("MenuConfig/Menus/Menu/Item/Item", "addMenuComponent", "net.sf.navigator.menu.MenuComponent");
97
98
99 digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item", "net.sf.navigator.menu.MenuComponent", "type");
100 digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item");
101 digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item", "addMenuComponent", "net.sf.navigator.menu.MenuComponent");
102
103
104 digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item", "net.sf.navigator.menu.MenuComponent", "type");
105 digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item");
106 digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item", "addMenuComponent", "net.sf.navigator.menu.MenuComponent");
107
108
109 digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item", "net.sf.navigator.menu.MenuComponent", "type");
110 digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item");
111 digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item", "addMenuComponent", "net.sf.navigator.menu.MenuComponent");
112
113
114 digester.addObjectCreate("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item/Item", "net.sf.navigator.menu.MenuComponent", "type");
115 digester.addSetProperties("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item/Item");
116 digester.addSetNext("MenuConfig/Menus/Menu/Item/Item/Item/Item/Item/Item", "addMenuComponent", "net.sf.navigator.menu.MenuComponent");
117
118 digester.addObjectCreate("MenuConfig/Displayers/Displayer", "net.sf.navigator.displayer.MenuDisplayerMapping", "mapping");
119 digester.addSetProperties("MenuConfig/Displayers/Displayer");
120 digester.addSetNext("MenuConfig/Displayers/Displayer", "addMenuDisplayerMapping", "net.sf.navigator.displayer.MenuDisplayerMapping");
121 digester.addSetProperty("MenuConfig/Displayers/Displayer/SetProperty", "property", "value");
122
123 return digester;
124 }
125
126 /**
127 * Adds a new menu. This is called when parsing the menu xml definition.
128 * @param menu The menu component to add.
129 */
130 public void addMenu(MenuComponent menu) {
131 if (menus.containsKey(menu.getName())) {
132 if (log.isDebugEnabled()) {
133 log.warn("Menu '" + menu.getName() + "' already exists in repository");
134 }
135 List children = (getMenu(menu.getName())).getComponents();
136 if (children != null && menu.getComponents() != null) {
137 for (Iterator it = children.iterator(); it.hasNext();) {
138 MenuComponent child = (MenuComponent) it.next();
139 menu.addMenuComponent(child);
140 }
141 }
142 }
143 menus.put(menu.getName(), menu);
144 }
145
146 /**
147 * Allows easy removal of a menu by its name.
148 * @param name
149 */
150 public void removeMenu(String name) {
151 menus.remove(name);
152 }
153
154 /**
155 * Allows easy removal of all menus, suggested use for users wanting to reload menus without having to perform
156 * a complete reload of the MenuRepository
157 */
158 public void removeAllMenus() {
159 menus.clear();
160 }
161
162 public void addMenuDisplayerMapping(MenuDisplayerMapping displayerMapping) {
163 displayers.put(displayerMapping.getName(), displayerMapping);
164 if (displayerMapping.getType().equals("net.sf.navigator.displayer.VelocityMenuDisplayer")) {
165 if (servletContext == null) {
166 log.error("ServletContext not set - can't initialize Velocity");
167 } else {
168 VelocityMenuDisplayer.initialize(servletContext);
169 }
170 }
171 }
172
173 /**
174 * This method is so menu repositories can retrieve displayers from the
175 * default repository specified in menu-config.xml
176 * @return the displayers specified in this repository
177 */
178 public LinkedMap getDisplayers() {
179 return displayers;
180 }
181
182 /**
183 * Allow the displayers to be set as a whole. This should only be used
184 * when copying the displayers from the default repository to a newly
185 * created repository.
186 * @param displayers
187 */
188 public void setDisplayers(LinkedMap displayers) {
189 this.displayers = displayers;
190 }
191
192 public void load() throws LoadableResourceException {
193 if (getServletContext() == null) {
194 throw new LoadableResourceException("no reference to servlet context found");
195 }
196
197 InputStream input = null;
198 Digester digester = initDigester();
199
200 try {
201 input = getServletContext().getResourceAsStream(config);
202 digester.parse(input);
203 } catch (Exception e) {
204 e.printStackTrace();
205 throw new LoadableResourceException("Error parsing resource file: " + config + " nested exception is: " + e.getMessage());
206 } finally {
207 try {
208 input.close();
209 } catch (Exception e) {
210 }
211 }
212 }
213
214 public void reload() throws LoadableResourceException {
215 menus.clear();
216 displayers.clear();
217 load();
218 }
219
220 public void setLoadParam(String loadParam) {
221 config = loadParam;
222 }
223
224 public String getLoadParam() {
225 return config;
226 }
227
228 public void setName(String name) {
229 this.name = name;
230 }
231
232 public String getName() {
233 return name;
234 }
235
236 public ServletContext getServletContext() {
237 return servletContext;
238 }
239
240 public void setServletContext(ServletContext context) {
241 this.servletContext = context;
242 }
243
244 /**
245 * Method getMenu. Get a subMenu beneath a root or parent menu. Will drill-down as deep as requested
246 * @param menuName - e.g grandParent.parent.menu
247 * @param delimiter - e.g. '.'
248 * @return MenuComponent
249 */
250 public MenuComponent getMenu(String menuName, String delimiter) {
251 MenuComponent parent = null;
252 StringTokenizer st = new StringTokenizer(menuName, delimiter);
253 boolean firstMenu = true;
254
255 while (st.hasMoreTokens()) {
256 if (firstMenu) {
257 parent = this.getMenu(st.nextToken());
258 firstMenu = false;
259 } else {
260 MenuComponent child = null;
261 String name = st.nextToken();
262 for (int a = 0; a < parent.getComponents().size(); a++) {
263 if (name.equals(((MenuComponent) parent.getComponents().get(a)).getName())) {
264 child = (MenuComponent) parent.getComponents().get(a);
265 a = parent.getComponents().size();
266 }
267 }
268 if (child != null) {
269 parent = child;
270 } else {
271 parent = null;
272 break;
273 }
274 }
275 }
276
277 return parent;
278 }
279
280 /**
281 * Method getMenuDepth.
282 * Get the depth of the deepest sub-menu within the requested top menu
283 * @param menuName - name of the top menu to check the menu depth
284 * @return int. If no menuName found return -1
285 */
286 public int getMenuDepth(String menuName) {
287
288 MenuComponent menu = this.getMenu(menuName);
289 if (menu == null)
290 return -1;
291 if (menu.getMenuComponents() == null)
292 return 1;
293 return menu.getMenuDepth();
294 }
295
296 /**
297 * Method getMenuDepth.
298 * Get the depth of the deepest sub-menu throughout all menus held in the repository
299 * @return int. If no menus return -1.
300 */
301 public int getMenuDepth() {
302 int currentDepth = 0;
303
304 List topMenus = this.getTopMenus();
305
306 if (topMenus == null)
307 return -1;
308 for (Iterator menu = topMenus.iterator(); menu.hasNext();) {
309 int depth = ((MenuComponent) menu.next()).getMenuDepth();
310 if (currentDepth < depth)
311 currentDepth = depth;
312 }
313 return currentDepth;
314 }
315
316 /**
317 * Method getTopMenusAsArray. Get menus as array rather than a List
318 * @return MenuComponent[]
319 */
320 public MenuComponent[] getTopMenusAsArray() {
321 List menuList = this.getTopMenus();
322 MenuComponent[] menus = new MenuComponent[menuList.size()];
323 for (int a = 0; a < menuList.size(); a++) {
324 menus[a] = (MenuComponent) menuList.get(a);
325 }
326
327 return menus;
328 }
329
330 /**
331 * Get a List of all the top menus' names
332 * @return List
333 */
334 public List getTopMenusNames() {
335 List menus = this.getTopMenus();
336 ArrayList names = new ArrayList();
337 for (Iterator iterator = menus.iterator(); iterator.hasNext();) {
338 MenuComponent menu = (MenuComponent) iterator.next();
339 names.add(menu.getName());
340 }
341 return names;
342 }
343
344 public void setBreadCrumbDelimiter(String string) {
345 breadCrumbDelimiter = string;
346 }
347
348 public void buildBreadCrumbs() {
349 if (breadCrumbDelimiter == null) {
350 throw new NullPointerException("No breadCrumbDelimiter present");
351 }
352 ArrayList menus = (ArrayList)this.getTopMenus();
353 for (Iterator iterator = menus.iterator(); iterator.hasNext();) {
354 MenuComponent menu = (MenuComponent)iterator.next();
355 menu.setBreadCrumb(breadCrumbDelimiter);
356 }
357 }
358
359 public void buildBreadCrumbs(String delimiter) {
360 this.breadCrumbDelimiter = delimiter;
361 buildBreadCrumbs();
362 }
363 }