0001: /*
0002: * argun 1.0
0003: * Web 2.0 delivery framework
0004: * Copyright (C) 2007 Hammurapi Group
0005: *
0006: * This program is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation; either
0009: * version 2 of the License, or (at your option) any later version.
0010: *
0011: * This program is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: *
0016: * You should have received a copy of the GNU Lesser General Public
0017: * License along with this library; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0019: *
0020: * URL: http://www.hammurapi.biz
0021: * e-Mail: support@hammurapi.biz
0022: */
0023: package biz.hammurapi.web.menu;
0024:
0025: import java.io.IOException;
0026: import java.io.StringReader;
0027: import java.lang.reflect.Constructor;
0028: import java.lang.reflect.InvocationHandler;
0029: import java.lang.reflect.InvocationTargetException;
0030: import java.lang.reflect.Method;
0031: import java.lang.reflect.Proxy;
0032: import java.net.URL;
0033: import java.sql.ResultSet;
0034: import java.sql.SQLException;
0035: import java.util.ArrayList;
0036: import java.util.Collection;
0037: import java.util.HashMap;
0038: import java.util.HashSet;
0039: import java.util.Iterator;
0040: import java.util.List;
0041: import java.util.Map;
0042: import java.util.Properties;
0043: import java.util.Set;
0044: import java.util.TreeSet;
0045:
0046: import javax.servlet.http.HttpServletRequest;
0047: import javax.servlet.http.HttpServletResponse;
0048: import javax.xml.parsers.ParserConfigurationException;
0049:
0050: import org.apache.log4j.Logger;
0051: import org.apache.xpath.CachedXPathAPI;
0052: import org.w3c.dom.Document;
0053: import org.w3c.dom.Element;
0054: import org.w3c.dom.Node;
0055: import org.w3c.dom.NodeList;
0056: import org.xml.sax.SAXException;
0057:
0058: import biz.hammurapi.authorization.AuthorizationProvider;
0059: import biz.hammurapi.cache.Cache;
0060: import biz.hammurapi.codegen.GenerationException;
0061: import biz.hammurapi.codegen.InjectingClassLoader;
0062: import biz.hammurapi.codegen.Interface;
0063: import biz.hammurapi.codegen.InterfacePool;
0064: import biz.hammurapi.codegen.InterfacePool.InterfaceDescriptor;
0065: import biz.hammurapi.config.ChainedContext;
0066: import biz.hammurapi.config.Component;
0067: import biz.hammurapi.config.ConfigurationException;
0068: import biz.hammurapi.config.Context;
0069: import biz.hammurapi.config.DomConfigurable;
0070: import biz.hammurapi.config.MapContext;
0071: import biz.hammurapi.config.PropertyParser;
0072: import biz.hammurapi.config.Wrapper;
0073: import biz.hammurapi.convert.CompositeConverter;
0074: import biz.hammurapi.sql.DataAccessObject;
0075: import biz.hammurapi.sql.SQLProcessor;
0076: import biz.hammurapi.sql.metadata.DefaultGenerationPolicy;
0077: import biz.hammurapi.sql.metadata.GenerationPolicy;
0078: import biz.hammurapi.web.HammurapiWebException;
0079: import biz.hammurapi.web.RequestContext;
0080: import biz.hammurapi.web.eval.EvaluationResult;
0081: import biz.hammurapi.web.eval.EvaluatorFactory;
0082: import biz.hammurapi.web.interaction.sql.Interaction;
0083: import biz.hammurapi.web.interaction.sql.InteractionEngine;
0084: import biz.hammurapi.web.menu.matchers.ExactUriMatcher;
0085: import biz.hammurapi.web.menu.matchers.MatchResult;
0086: import biz.hammurapi.web.menu.matchers.OrRequestMatcher;
0087: import biz.hammurapi.web.menu.matchers.RequestMatcher;
0088: import biz.hammurapi.web.menu.matchers.UriMatcher;
0089: import biz.hammurapi.web.menu.sql.Adornment;
0090: import biz.hammurapi.web.menu.sql.ItemReference;
0091: import biz.hammurapi.web.menu.sql.MenuEngine;
0092: import biz.hammurapi.web.menu.sql.MenuHelpTopics;
0093: import biz.hammurapi.web.menu.sql.MenuHelpTopicsImpl;
0094: import biz.hammurapi.web.menu.sql.MenuNavigatorTemplate;
0095: import biz.hammurapi.web.menu.sql.MenuParameter;
0096: import biz.hammurapi.web.menu.sql.Permission;
0097: import biz.hammurapi.web.menu.sql.ResourcePattern;
0098: import biz.hammurapi.web.menu.sql.XmenuImpl;
0099: import biz.hammurapi.web.security.UserAuthorizationProvider;
0100: import biz.hammurapi.web.util.Escaper;
0101: import biz.hammurapi.wrap.WrapperHandler;
0102: import biz.hammurapi.xml.dom.AbstractDomObject;
0103: import biz.hammurapi.xml.dom.DOMUtils;
0104:
0105: public class Menu extends XmenuImpl implements DataAccessObject,
0106: RequestMatcher, ChainedContext {
0107: private static final String CONTEXT_PREFIX = "context:";
0108: private static final String TEMPLATE_PREFIX = "template:";
0109: private static final String TOPIC_CONTENT_URL_PART = "/topicContent?id=T";
0110: private static final String MENU_HELP_URL_PART = "/menuHelp/help.html?id=";
0111: public static final String MT_ROOT = "root";
0112: public static final String MT_FORM_HANDLER = "formHandler";
0113: public static final String MT_RESOURCE = "resource";
0114:
0115: private static final Logger logger = Logger.getLogger(Menu.class);
0116: private static final String MENU_HELP_ACTIONS_GLOBAL_PATH = "global:db/MenuHelpActionsPath";
0117:
0118: private static final String INTERACTION_CONTROLLER_URI = "/system/interaction.InterActions/start?interactionId=";
0119:
0120: public Menu() {
0121: super ();
0122: }
0123:
0124: public Menu(boolean force) {
0125: super (force);
0126: }
0127:
0128: public Menu(Element holder, boolean force)
0129: throws ConfigurationException {
0130: super (holder, force);
0131: }
0132:
0133: public Menu(Element holder, Properties nameMap,
0134: CachedXPathAPI cxpa, boolean force)
0135: throws ConfigurationException {
0136: super (holder, nameMap, cxpa, force);
0137: }
0138:
0139: public Menu(ResultSet rs) throws SQLException {
0140: super (rs);
0141: }
0142:
0143: private Collection children;
0144: private Map itemMap = new HashMap();
0145: private Map actionMap = new HashMap();
0146: private Map idMap;
0147: private Map xidMap;
0148: private Collection functions;
0149: private Map navigatorTemplates = new HashMap();
0150: private Map helpTopics = new HashMap();
0151: private Help help = new Help(this );
0152:
0153: private void loadHelpMountPoints(Context context) {
0154: synchronized (helpMountPoints) {
0155: if (!helpMountPoints.isEmpty()) {
0156: PropertyParser pp = new PropertyParser(context, false);
0157: Iterator it = helpMountPoints.iterator();
0158: while (it.hasNext()) {
0159: MenuHelpTopics mht = (MenuHelpTopics) it.next();
0160: try {
0161: String mountUrlStr = pp
0162: .parse(mht.getTopicUrl());
0163: Document doc;
0164: if (mountUrlStr.startsWith(CONTEXT_PREFIX)) {
0165: Object xml = context
0166: .get(mountUrlStr
0167: .substring(CONTEXT_PREFIX
0168: .length()));
0169: if (xml == null) {
0170: logger.error("Invalid mount URL: "
0171: + mountUrlStr);
0172: continue;
0173: }
0174: doc = DOMUtils.parse(new StringReader(xml
0175: .toString()));
0176: } else {
0177: URL mountUrl = new URL(mountUrlStr);
0178: doc = DOMUtils.parse(mountUrl.openStream());
0179: }
0180: doc.getDocumentElement().setAttribute("name",
0181: mht.getName());
0182: mount(doc.getDocumentElement(), null, mht
0183: .getName().indexOf("#") != -1,
0184: mountUrlStr);
0185: } catch (Exception e) {
0186: logger.error("Could not load mount point from "
0187: + mht.getTopicUrl() + " : " + e, e);
0188: }
0189: it.remove();
0190: }
0191: }
0192: }
0193: }
0194:
0195: /**
0196: * @return Root help descriptor for this item.
0197: */
0198: public Help getHelp(Context context) {
0199: loadHelpMountPoints(context);
0200: return help;
0201: }
0202:
0203: public MenuHelpTopics getHelpTopic(String name, Context context) {
0204: loadHelpMountPoints(context);
0205: return (MenuHelpTopics) helpTopics.get(name);
0206: }
0207:
0208: /**
0209: * Loads dependent objects.
0210: */
0211: public void setSQLProcessor(SQLProcessor processor)
0212: throws SQLException {
0213: final MenuEngine engine = new MenuEngine(processor);
0214: if (getParent() == null) {
0215: interactionOffset = engine.getMaxMenu() + 1;
0216: logger.info("Interaction offset: " + interactionOffset);
0217: nextVirtualId = interactionOffset
0218: + engine.getMaxInteraction();
0219: }
0220: children = engine.getXmenuByParent(new Integer(getId()),
0221: new ArrayList(), Menu.class);
0222: Iterator it = children.iterator();
0223: while (it.hasNext()) {
0224: ((Menu) it.next()).setParent(new Integer(getId()));
0225: }
0226: it = engine.getItemReferenceByItem(getId()).iterator();
0227: while (it.hasNext()) {
0228: ItemReference ir = (ItemReference) it.next();
0229: Menu referenced = (Menu) engine.getXmenu(ir.getRefId(),
0230: Menu.class);
0231: if (!isBlank(ir.getName())) {
0232: referenced.setName(ir.getName());
0233: }
0234: referenced.setQueryTemplate(ir.getQueryTemplate());
0235: referenced.setScope(ir.getScope());
0236: if (!isBlank(ir.getTitle())) {
0237: referenced.setTitle(ir.getTitle());
0238: }
0239: referenced.setItemPosition(ir.getRefPosition());
0240: // Integer offset = new Integer((getMaxMenuId()+1)*(ir.getId()+1));
0241: // referenced.refOffset = offset;
0242: // referenced.setId(referenced.getRefOffset()+referenced.getId());
0243: children.add(referenced);
0244: }
0245:
0246: InteractionEngine iEngine = new InteractionEngine(processor);
0247: it = iEngine.getInteractionByOwner(getId()).iterator();
0248: while (it.hasNext()) {
0249: Interaction interaction = (Interaction) it.next();
0250: Menu iMenu = new Menu();
0251: iMenu.setParent(new Integer(getId()));
0252: iMenu
0253: .setUri(INTERACTION_CONTROLLER_URI
0254: + interaction.getId()/*+"&refOffset="+getRefOffset()*/);
0255: iMenu.setName(interaction.getName());
0256: iMenu.setScope(interaction.getScope());
0257: iMenu.setGuid(interaction.getGuid());
0258: iMenu.setId(/*getRefOffset()+*/getInteractionOffset()
0259: + interaction.getId());
0260: interaction.setLastModified(interaction.getLastModified());
0261: iMenu.setSQLProcessor(processor);
0262: iMenu.setType("item");
0263: children.add(iMenu);
0264: }
0265:
0266: adornments = engine
0267: .getAdornmentByItem(getId(), new ArrayList());
0268: resourcePatterns = engine.getResourcePatternByItem(getId(),
0269: new ArrayList());
0270: colors = engine.getMenuColorByMenu(getId(), new ArrayList());
0271: permissions = engine.getPermission(getId(), new ArrayList());
0272: parameters = engine.getMenuParameterByMenu(getId(),
0273: new ArrayList());
0274: it = parameters.iterator();
0275: while (it.hasNext()) {
0276: MenuParameter mp = (MenuParameter) it.next();
0277: setAttribute(mp.getName(), mp.getParameterValue());
0278: }
0279:
0280: functions = engine.getMenuFunctionByMenu(getId(),
0281: Function.class);
0282:
0283: it = engine.getMenuNavigatorTemplateByMenu(getId()).iterator();
0284: while (it.hasNext()) {
0285: MenuNavigatorTemplate mnt = (MenuNavigatorTemplate) it
0286: .next();
0287: navigatorTemplates.put(mnt.getName(), mnt);
0288: }
0289:
0290: it = engine.getMenuHelpTopics(getId()).iterator();
0291: while (it.hasNext()) {
0292: MenuHelpTopics mht = (MenuHelpTopics) it.next();
0293: if ("Mount point".equals(mht.getType())) {
0294: if (isBlank(mht.getTopicUrl())) {
0295: logger
0296: .error("Could not load mount point, URL is blank.");
0297: } else {
0298: helpMountPoints.add(mht);
0299: }
0300: } else {
0301: helpTopics.put(mht.getName(), mht);
0302: help.add("main".equals(mht.getName()) ? null : mht
0303: .getName(), mht);
0304: }
0305: }
0306: }
0307:
0308: /**
0309: * Walks up the hierarchy until finds the needed attribute.
0310: */
0311: public Object getAttribute(Object key) {
0312: Object ret = super .getAttribute(key);
0313: if (ret != null) {
0314: return ret;
0315: }
0316: return parent == null ? parent.getAttribute(key) : null;
0317: }
0318:
0319: private Collection adornments;
0320: private Collection resourcePatterns;
0321: private Collection colors;
0322: private Collection permissions;
0323: private Collection parameters;
0324: private Menu[] path;
0325:
0326: private OrRequestMatcher resourcePatternMatcher;
0327: private UriMatcher rootMatcher;
0328: private UriMatcher formHandlerMatcher;
0329:
0330: private Menu parent;
0331:
0332: private boolean started;
0333: private boolean startResult;
0334: private boolean blankUri;
0335: private Object engine;
0336: private Object runtimeEngine;
0337:
0338: public Object getEngine() {
0339: return engine;
0340: }
0341:
0342: /**
0343: * @return Generated engine instance, which combines
0344: * methods from engine class and functions.
0345: */
0346: public Object getRuntimeEngine() {
0347: if (runtimeEngine == null) {
0348: return parent == null ? null : parent.getRuntimeEngine();
0349: }
0350: return runtimeEngine;
0351: }
0352:
0353: private Map functionMap;
0354:
0355: private class FunctionKey extends ArrayList {
0356: public FunctionKey(String functionName, int parameterCount) {
0357: super ();
0358: add(functionName);
0359: add(new Integer(parameterCount));
0360: }
0361: }
0362:
0363: /**
0364: * @param request
0365: * @param response
0366: * @return Runtime engine, which is aware of its invocation context.
0367: */
0368: public Object getRuntimeEngine(final HttpServletRequest request,
0369: final HttpServletResponse response) {
0370: final Object parentRuntimeEngine = parent == null ? null
0371: : parent.getRuntimeEngine(request, response);
0372:
0373: if (functions.isEmpty()) {
0374: return parentRuntimeEngine;
0375: }
0376:
0377: // Generate own engine
0378: InvocationHandler ih = new InvocationHandler() {
0379:
0380: public Object invoke(Object proxy, Method method,
0381: Object[] args) throws Throwable {
0382: if (Object.class.getName().equals(
0383: method.getDeclaringClass().getName())) {
0384: return method.invoke(functionMap, args); // Delegate Object's methods to function map.
0385: }
0386: Object key = new FunctionKey(method.getName(), method
0387: .getParameterTypes().length);
0388: Function function = (Function) functionMap.get(key);
0389: if (function != null) {
0390: return function.invoke(args, request, response);
0391: }
0392:
0393: if (parentRuntimeEngine == null) {
0394: throw new HammurapiWebException("Function "
0395: + method + " not found in runtime engine");
0396: }
0397:
0398: return method.invoke(parentRuntimeEngine, args); // Delegate to parent
0399: }
0400:
0401: };
0402:
0403: return Proxy.newProxyInstance(getInjectingClassLoader(),
0404: runtimeEngineInterfaces, ih);
0405: }
0406:
0407: /**
0408: * Starts root menu.
0409: */
0410: public boolean start(AuthorizationProvider authorizationProvider,
0411: Context context) throws HammurapiWebException {
0412: return start(null, new HashSet(), authorizationProvider,
0413: context);
0414: }
0415:
0416: /**
0417: * Starts menu item.
0418: * @param parent Parent item.
0419: * @param authorizationProvider Authorization provider
0420: * @param contextPath Context path to prepend to uri's
0421: * @return false if authorization provider doesn't have permissions to access this item.
0422: * @throws HammurapiWebException
0423: */
0424: private boolean start(Menu parent, Set idSet,
0425: AuthorizationProvider authorizationProvider, Context context)
0426: throws HammurapiWebException {
0427: if (!started) {
0428: startResult = _start(parent, idSet, authorizationProvider,
0429: context);
0430: }
0431:
0432: return startResult;
0433: }
0434:
0435: /**
0436: * Interfaces implemented by runtime engine
0437: */
0438: private Class[] runtimeEngineInterfaces;
0439:
0440: private boolean _start(final Menu parent, Set idSet,
0441: AuthorizationProvider authorizationProvider, Context context)
0442: throws HammurapiWebException {
0443: if (getIsDisabled()) {
0444: return false;
0445: }
0446:
0447: Integer originalId = new Integer(getId());
0448: if (!idSet.add(originalId)) {
0449: logger.warn("Circular reference, menu " + getId()
0450: + " is a child of self");
0451: return false;
0452: }
0453:
0454: if (authorizationProvider != null) {
0455: boolean accessGranted = false;
0456:
0457: // Check direct assignment of permissions
0458: if (!accessGranted
0459: && authorizationProvider instanceof UserAuthorizationProvider) {
0460: Boolean hasAccess = ((UserAuthorizationProvider) authorizationProvider)
0461: .hasAccess(getId());
0462: if (Boolean.TRUE.equals(hasAccess)) {
0463: accessGranted = true;
0464: }
0465:
0466: if (Boolean.FALSE.equals(hasAccess)) {
0467: return false;
0468: }
0469: }
0470:
0471: // If user is not in ACL and is not directly assigned menu id, then check class permissions.
0472: if (!accessGranted) {
0473: Iterator it = permissions.iterator();
0474: while (it.hasNext()) {
0475: Permission p = (Permission) it.next();
0476: if (!authorizationProvider.hasClassPermission(p
0477: .getClassName(), p.getActionName())) {
0478: return false;
0479: }
0480: }
0481: }
0482:
0483: boolean strictSecurity = "strict".equals(context
0484: .get("db/menu-security"));
0485:
0486: // Protect access to the first level menus if permissions haven't been granted explicitly.
0487: if (strictSecurity && !accessGranted && parent != null
0488: && parent.parent == null) {
0489: return false;
0490: }
0491: }
0492:
0493: this .parent = parent;
0494: if (parent == null) {
0495: path = new Menu[] { this };
0496: matchCache = new HashMap();
0497: injectingClassLoader = new InjectingClassLoader(this
0498: .getClass().getClassLoader());
0499: interfacePool = new InterfacePool();
0500: cache = (Cache) context.get("db/cache");
0501: generationPolicy = new DefaultGenerationPolicy();
0502: guidMap = new HashMap();
0503: idMap = new HashMap();
0504: xidMap = new HashMap();
0505: } else {
0506: path = new Menu[parent.getPath().length + 1];
0507: System.arraycopy(parent.getPath(), 0, path, 0,
0508: path.length - 1);
0509: path[path.length - 1] = this ;
0510: if (!isBlank(parent.getKeywords())) {
0511: setKeywords(parent.getKeywords() + ", " + getKeywords());
0512: }
0513:
0514: Collection parentAdornmentsToAdd = new ArrayList();
0515: Iterator it = parent.adornments.iterator();
0516: Z: while (it.hasNext()) {
0517: Adornment pa = (Adornment) it.next();
0518: Iterator sit = adornments.iterator();
0519: while (sit.hasNext()) {
0520: Adornment a = (Adornment) sit.next();
0521: if (pa.getName().equals(a.getName())
0522: && a.getSuppress()) {
0523: continue Z;
0524: }
0525: }
0526: parentAdornmentsToAdd.add(pa);
0527: }
0528: adornments.addAll(parentAdornmentsToAdd);
0529: }
0530:
0531: mapItem(this );
0532:
0533: if (!isBlank(getGuid())) {
0534: putToGuidMap(getGuid(), this );
0535: }
0536:
0537: String parentBaseUri = null;
0538: if (parent != null) {
0539: if (parent.getUri().endsWith("/")) {
0540: parentBaseUri = parent.getUri();
0541: } else {
0542: int idx = parent.getUri().lastIndexOf('/');
0543: if (idx != -1) {
0544: parentBaseUri = parent.getUri().substring(0,
0545: idx + 1);
0546: }
0547: }
0548: }
0549:
0550: PropertyParser selfContextPropertyParser = new PropertyParser(
0551: this , false);
0552:
0553: if (isBlank(getUri())) {
0554: setUri("xMenu" + getId() + ".html");
0555: }
0556:
0557: if (isBlank(getFormHandlerUri())) {
0558: setFormHandlerUri("formHandler" + getId() + ".html");
0559: }
0560:
0561: // Parse XID to expand entries like ${parent:XID}.viewSomthing
0562: setXid(selfContextPropertyParser.parse(getXid()));
0563:
0564: if (isBlank(getUri()) || "#".equals(getUri().trim())) {
0565: blankUri = true;
0566: setUri(parent.getUri()); // so children uri's are calculated relative to parent's uri.
0567: } else {
0568: String parsedUri = selfContextPropertyParser
0569: .parse(getUri());
0570: rootMatcher = new ExactUriMatcher(parsedUri, parentBaseUri,
0571: getMatchQueryString(), getWeight());
0572: setUri(rootMatcher.getAbsolutePattern()); // Override relative uri with absolute value.
0573: }
0574:
0575: String baseUri = null;
0576: if (getUri().endsWith("/")) {
0577: baseUri = getUri();
0578: } else {
0579: int idx = getUri().lastIndexOf('/');
0580: if (idx != -1) {
0581: baseUri = getUri().substring(0, idx + 1);
0582: }
0583: }
0584:
0585: if (!resourcePatterns.isEmpty()) {
0586: resourcePatternMatcher = new OrRequestMatcher();
0587: Iterator it = resourcePatterns.iterator();
0588: while (it.hasNext()) {
0589: ResourcePattern rp = (ResourcePattern) it.next();
0590: resourcePatternMatcher.addMatcher(UriMatcher
0591: .newMatcher(rp.getPatternLanguage(), rp
0592: .getPattern(), baseUri, rp
0593: .getMatchQueryString(), getWeight()));
0594: }
0595: }
0596:
0597: if (!isBlank(getFormHandlerUri())) {
0598: String parsedFormHandlerUri = selfContextPropertyParser
0599: .parse(getFormHandlerUri());
0600: formHandlerMatcher = new ExactUriMatcher(
0601: parsedFormHandlerUri, baseUri, false, 0);
0602: setFormHandlerUri(formHandlerMatcher.getAbsolutePattern()); // Override relative uri with absolute value.
0603: }
0604:
0605: if (!isBlank(getEngineClass())) {
0606: try {
0607: Class clazz = Class.forName(getEngineClass());
0608: if (isBlank(getEngineConfig())) {
0609: engine = clazz.newInstance();
0610: } else {
0611: // Attempt to construct class from value
0612: Constructor[] constructors = clazz
0613: .getConstructors();
0614: for (int i = 0; i < constructors.length; i++) {
0615: if (constructors[i].getParameterTypes().length == 1
0616: && constructors[i].getParameterTypes()[0]
0617: .equals(String.class)) {
0618: engine = constructors[i]
0619: .newInstance(new Object[] { getEngineConfig() });
0620: break;
0621: }
0622: }
0623:
0624: if (engine == null) {
0625: engine = clazz.newInstance();
0626:
0627: if (DomConfigurable.class
0628: .isAssignableFrom(clazz)) {
0629: ((DomConfigurable) engine)
0630: .configure(DOMUtils.parse(
0631: new StringReader(
0632: getEngineConfig()))
0633: .getDocumentElement(), this );
0634: } else {
0635: engine = CompositeConverter
0636: .getDefaultConverter().convert(
0637: getEngineConfig(), clazz,
0638: false);
0639: }
0640: }
0641: }
0642:
0643: if (engine instanceof Wrapper) {
0644: engine = ((Wrapper) engine).getMaster();
0645: }
0646: } catch (InstantiationException e) {
0647: throw new HammurapiWebException(
0648: "Cannot instantiate engine '"
0649: + getEngineClass() + " with config "
0650: + getEngineConfig() + ": " + e, e);
0651: } catch (IllegalAccessException e) {
0652: throw new HammurapiWebException(
0653: "Cannot instantiate engine '"
0654: + getEngineClass() + " with config "
0655: + getEngineConfig() + ": " + e, e);
0656: } catch (ClassNotFoundException e) {
0657: throw new HammurapiWebException(
0658: "Cannot instantiate engine '"
0659: + getEngineClass() + " with config "
0660: + getEngineConfig() + ": " + e, e);
0661: } catch (SecurityException e) {
0662: throw new HammurapiWebException(
0663: "Cannot instantiate engine '"
0664: + getEngineClass() + " with config "
0665: + getEngineConfig() + ": " + e, e);
0666: } catch (InvocationTargetException e) {
0667: throw new HammurapiWebException(
0668: "Cannot instantiate engine '"
0669: + getEngineClass() + " with config "
0670: + getEngineConfig() + ": " + e, e);
0671: } catch (ConfigurationException e) {
0672: throw new HammurapiWebException(
0673: "Cannot instantiate engine '"
0674: + getEngineClass() + " with config "
0675: + getEngineConfig() + ": " + e, e);
0676: } catch (SAXException e) {
0677: throw new HammurapiWebException(
0678: "Cannot instantiate engine '"
0679: + getEngineClass() + " with config "
0680: + getEngineConfig() + ": " + e, e);
0681: } catch (IOException e) {
0682: throw new HammurapiWebException(
0683: "Cannot instantiate engine '"
0684: + getEngineClass() + " with config "
0685: + getEngineConfig() + ": " + e, e);
0686: } catch (ParserConfigurationException e) {
0687: throw new HammurapiWebException(
0688: "Cannot instantiate engine '"
0689: + getEngineClass() + " with config "
0690: + getEngineConfig() + ": " + e, e);
0691: }
0692: }
0693:
0694: if (!functions.isEmpty()) {
0695: functionMap = new HashMap();
0696:
0697: // Create runtime engine here.
0698: InterfaceDescriptor ed = getInterfacePool().addInterface(
0699: this .getClass().getName() + "$RuntimeEngine"
0700: + getId(), null);
0701: Iterator it = functions.iterator();
0702: while (it.hasNext()) {
0703: Function function = (Function) it.next();
0704: function.start(context, this );
0705: Object key = new FunctionKey(function.getName(),
0706: function.getParameterCount());
0707: if (functionMap.containsKey(key)) {
0708: throw new HammurapiWebException(
0709: "Duplicate function " + function.getName()
0710: + " with "
0711: + function.getParameterCount()
0712: + " parameters");
0713: }
0714: functionMap.put(key, function);
0715: StringBuffer funcDef = new StringBuffer(
0716: "public java.lang.Object ");
0717: funcDef.append(function.getName());
0718: funcDef.append("(");
0719: for (int i = 0; i < function.getParameterCount(); ++i) {
0720: if (i > 0) {
0721: funcDef.append(",");
0722: }
0723: funcDef.append("java.lang.Object");
0724: }
0725: funcDef.append(") throws ");
0726: funcDef.append(HammurapiWebException.class.getName());
0727: ed.addMethod(funcDef.toString());
0728: }
0729:
0730: try {
0731: if (ed.isMaster()) {
0732: // There is no master - need to generate
0733: Collection super Interfaces = ed
0734: .getSuperInterfaces(null);
0735: StringBuffer iName = new StringBuffer(
0736: "public interface ");
0737: iName.append(ed.getName());
0738: if (!super Interfaces.isEmpty()) {
0739: iName.append(" extends ");
0740: Iterator eit = super Interfaces.iterator();
0741: while (eit.hasNext()) {
0742: iName.append(eit.next());
0743: if (eit.hasNext()) {
0744: iName.append(", ");
0745: }
0746: }
0747: }
0748: Interface rsInterface = new Interface(iName
0749: .toString(), "Runtime engine interface",
0750: null);
0751: it = ed.getOwnMethods().iterator();
0752: while (it.hasNext()) {
0753: rsInterface.addMethod((String) it.next(), null,
0754: "Runtime function", null);
0755: }
0756:
0757: getInjectingClassLoader().consume(rsInterface);
0758:
0759: }
0760:
0761: final Class runtimeEngineInterface = getInjectingClassLoader()
0762: .loadClass(ed.getMaster().getName());
0763: final Object parentRuntimeEngine = parent == null ? null
0764: : parent.getRuntimeEngine();
0765:
0766: if (parentRuntimeEngine == null) {
0767: runtimeEngineInterfaces = new Class[] { runtimeEngineInterface };
0768: } else {
0769: if (runtimeEngineInterface
0770: .isInstance(parentRuntimeEngine)) {
0771: runtimeEngineInterfaces = WrapperHandler
0772: .getClassInterfaces(parentRuntimeEngine
0773: .getClass());
0774: } else {
0775: Class[] parentInterfaces = WrapperHandler
0776: .getClassInterfaces(parentRuntimeEngine
0777: .getClass());
0778: runtimeEngineInterfaces = new Class[parentInterfaces.length + 1];
0779: runtimeEngineInterfaces[0] = runtimeEngineInterface;
0780: System.arraycopy(parentInterfaces, 0,
0781: runtimeEngineInterfaces, 1,
0782: parentInterfaces.length);
0783: }
0784: }
0785:
0786: // Generate own engine
0787: InvocationHandler ih = new InvocationHandler() {
0788:
0789: public Object invoke(Object proxy, Method method,
0790: Object[] args) throws Throwable {
0791: if (Object.class.getName().equals(
0792: method.getDeclaringClass().getName())) {
0793: return method.invoke(functionMap, args); // Delegate Object's methods to function map.
0794: }
0795: Object key = new FunctionKey(method.getName(),
0796: method.getParameterTypes().length);
0797: Function function = (Function) functionMap
0798: .get(key);
0799: if (function != null) {
0800: return function.invoke(args);
0801: }
0802:
0803: if (parentRuntimeEngine == null) {
0804: throw new HammurapiWebException("Function "
0805: + method
0806: + " not found in runtime engine "
0807: + runtimeEngineInterface.getName());
0808: }
0809: return method.invoke(parentRuntimeEngine, args); // Delegate to parent
0810: }
0811:
0812: };
0813:
0814: runtimeEngine = Proxy.newProxyInstance(
0815: getInjectingClassLoader(),
0816: runtimeEngineInterfaces, ih);
0817: } catch (GenerationException e) {
0818: throw new HammurapiWebException(
0819: "Could not generate runtime engine: " + e, e);
0820: } catch (ClassNotFoundException e) {
0821: throw new HammurapiWebException(
0822: "Could not generate runtime engine: " + e, e);
0823: }
0824: }
0825:
0826: // Start children. Remove unaccessible children.
0827: Iterator it = children.iterator();
0828: while (it.hasNext()) {
0829: Menu child = (Menu) it.next();
0830: if (child
0831: .start(this , idSet, authorizationProvider, context)) {
0832: if ("action".equals(child.getType())) {
0833: if (isBlank(child.getXid())) {
0834: logger.warn("Action without XID: "
0835: + child.getId());
0836: } else {
0837: actionMap.put(child.getXid(), child);
0838: }
0839: } else {
0840: if ("menu".equals(child.getType())) {
0841: child.setType("item"); // To simplify stylesheets.
0842: }
0843:
0844: itemMap.put(child.getName(), child);
0845: }
0846: } else {
0847: it.remove();
0848: }
0849: }
0850:
0851: // Overwrite matchQueryString with OR from patterns and children
0852: if (!getMatchQueryString()) {
0853: setMatchQueryString((rootMatcher != null && rootMatcher
0854: .isMatchQueryString())
0855: || (resourcePatternMatcher != null && resourcePatternMatcher
0856: .isMatchQueryString())
0857: || (formHandlerMatcher != null && formHandlerMatcher
0858: .isMatchQueryString()));
0859: }
0860:
0861: it = children.iterator();
0862: while (!getMatchQueryString() && it.hasNext()) {
0863: setMatchQueryString(((Menu) it.next()).isMatchQueryString());
0864: }
0865:
0866: idSet.remove(originalId);
0867: return true;
0868: }
0869:
0870: private static int mountPointsCounter = Integer.MAX_VALUE;
0871: private Collection helpMountPoints = new ArrayList();
0872:
0873: private void mount(Element helpElement, String parentPath,
0874: boolean isSection, String baseURL) {
0875: if (!isBlank(helpElement.getAttribute("name"))
0876: && helpElement.hasAttribute("href")) { // Skip blank topics, go straight to children
0877: MenuHelpTopicsImpl mhti = new MenuHelpTopicsImpl();
0878: mhti.setType("URL");
0879: mhti.setName(parentPath == null ? helpElement
0880: .getAttribute("name") : parentPath
0881: + (isSection ? "#" : "/")
0882: + helpElement.getAttribute("name"));
0883: synchronized (this ) {
0884: mhti.setId(mountPointsCounter--);
0885: }
0886: mhti.setTopicUrl(isSection ? baseURL + "#"
0887: + helpElement.getAttribute("href") : helpElement
0888: .getAttribute("href"));
0889: if (helpElement.hasAttribute("title")) {
0890: mhti.setTitle(helpElement.getAttribute("title"));
0891: }
0892: String originalName = mhti.getName();
0893: for (int i = 2; helpTopics.containsKey(mhti.getName()); ++i) {
0894: mhti.setName(originalName + " (" + i + ")");
0895: }
0896: helpTopics.put(mhti.getName(), mhti);
0897: help.add("main".equals(mhti.getName()) ? null : mhti
0898: .getName(), mhti);
0899: parentPath = mhti.getName();
0900: if (!isSection) {
0901: baseURL = mhti.getTopicUrl();
0902: }
0903: } else {
0904: logger
0905: .warn("Help mount point is missing either name or href attribute");
0906: }
0907:
0908: NodeList nl = helpElement.getChildNodes();
0909: for (int i = 0, l = nl.getLength(); i < l; ++i) {
0910: Node n = nl.item(i);
0911: if (n instanceof Element && "topic".equals(n.getNodeName())) {
0912: mount((Element) n, parentPath, false, baseURL);
0913: }
0914: }
0915: nl = helpElement.getChildNodes();
0916: for (int i = 0, l = nl.getLength(); i < l; ++i) {
0917: Node n = nl.item(i);
0918: if (n instanceof Element
0919: && "section".equals(n.getNodeName())) {
0920: mount((Element) n, parentPath, true, baseURL);
0921: }
0922: }
0923: }
0924:
0925: private void mapItem(Menu menu) {
0926: if (parent == null) {
0927: if (menu.getXid() != null) {
0928: xidMap.put(menu.getXid(), menu);
0929: }
0930: Integer mid = new Integer(menu.getId());
0931: if (idMap.containsKey(mid)) {
0932: menu.setId(nextVirtualId());
0933: logger.info("Duplicate item id, changing " + mid
0934: + " to " + menu.getId());
0935: mid = new Integer(menu.getId());
0936: }
0937: idMap.put(mid, menu);
0938: } else {
0939: parent.mapItem(menu);
0940: }
0941: }
0942:
0943: public void stop() throws ConfigurationException {
0944:
0945: Iterator it = children.iterator();
0946: while (it.hasNext()) {
0947: ((Component) it.next()).stop();
0948: }
0949:
0950: }
0951:
0952: /**
0953: * Renders menu to DOM
0954: * @param request
0955: * @param holder
0956: */
0957: public void toDom(Element holder, HttpServletRequest request) {
0958: if (enabled()) {
0959: super .toDom(holder);
0960: String href = (String) get("href", new RequestContext(
0961: request));
0962: if (href != null) {
0963: holder.setAttribute("href", href);
0964: }
0965: if (!adornments.isEmpty()) {
0966: DOMUtils.toDom(adornments, "adornments", holder);
0967: }
0968: if (!colors.isEmpty()) {
0969: DOMUtils.toDom(colors, "colors", holder);
0970: }
0971: if (!parameters.isEmpty()) {
0972: DOMUtils.toDom(parameters, "parameters", holder);
0973: }
0974: Iterator it = children.iterator();
0975: while (it.hasNext()) {
0976: Menu child = (Menu) it.next();
0977: Element childHolder = AbstractDomObject.addElement(
0978: holder, child.getType());
0979: child.toDom(childHolder, request);
0980: }
0981: }
0982: }
0983:
0984: public Menu findItemById(String id) {
0985: if (id.equals(getXid())) {
0986: return enabled() ? this : null;
0987: }
0988:
0989: Iterator it = children.iterator();
0990: while (it.hasNext()) {
0991: Menu ret = ((Menu) it.next()).findItemById(id);
0992: if (ret != null) {
0993: return ret.enabled() ? ret : null;
0994: }
0995: }
0996:
0997: return null;
0998: }
0999:
1000: private Map matchCache;
1001:
1002: private List _match(HttpServletRequest request) {
1003: List ret = new ArrayList();
1004: if (enabled()) {
1005: if (rootMatcher != null
1006: && !rootMatcher.match(request).isEmpty()) {
1007: MatchResult matchResult = new MatchResult(this ,
1008: rootMatcher.isMatchQueryString(), getWeight());
1009: matchResult.setAttribute("type", MT_ROOT);
1010: ret.add(matchResult);
1011: } else if (formHandlerMatcher != null
1012: && !formHandlerMatcher.match(request).isEmpty()) {
1013: MatchResult matchResult = new MatchResult(this ,
1014: formHandlerMatcher.isMatchQueryString(),
1015: getWeight());
1016: matchResult.setAttribute("type", MT_FORM_HANDLER);
1017: ret.add(matchResult);
1018: } else if (resourcePatternMatcher != null
1019: && !resourcePatternMatcher.match(request).isEmpty()) {
1020: MatchResult matchResult = new MatchResult(this ,
1021: resourcePatternMatcher.isMatchQueryString(),
1022: getWeight());
1023: matchResult.setAttribute("type", MT_RESOURCE);
1024: ret.add(matchResult);
1025: }
1026:
1027: Iterator it = children.iterator();
1028: while (it.hasNext()) {
1029: ret.addAll(((Menu) it.next()).match(request));
1030: }
1031: }
1032: return ret;
1033: }
1034:
1035: private int lastMatch;
1036: private int matchCounter;
1037:
1038: public List match(HttpServletRequest request) {
1039: List ret = null;
1040:
1041: // Root item caches matches
1042: if (matchCache == null) {
1043: ret = _match(request);
1044: } else {
1045: String key = request.getRequestURI();
1046:
1047: if (isMatchQueryString()) {
1048: String queryString = request.getQueryString();
1049: if (queryString != null) {
1050: key += "?" + queryString;
1051: }
1052: }
1053:
1054: ret = (List) matchCache.get(key);
1055: if (ret == null) {
1056: ret = _match(request);
1057: if (!ret.isEmpty()) {
1058: matchCache.put(key, ret);
1059: }
1060: }
1061:
1062: ++matchCounter;
1063: Map parameterMap = request.getParameterMap();
1064:
1065: Iterator it = ret.iterator();
1066: while (it.hasNext()) {
1067: Menu matchedMenu = ((Menu) ((MatchResult) it.next())
1068: .getMatcher());
1069: if ("GET".equalsIgnoreCase(request.getMethod())) {
1070: matchedMenu.matchParameters.putAll(parameterMap);
1071: }
1072: matchedMenu.setLastMatch(matchCounter);
1073: }
1074: }
1075:
1076: return ret;
1077: }
1078:
1079: private Map matchParameters = new HashMap();
1080:
1081: /**
1082: *
1083: * @param name
1084: * @return First value of given parameter at the time when item was matched. Parent matched
1085: * parameters are inherited.
1086: */
1087: public String getMatchParameter(String name) {
1088: String[] ret = (String[]) matchParameters.get(name);
1089: if (ret != null && ret.length > 0) {
1090: return ret[0];
1091: }
1092:
1093: return parent == null ? null : parent.getMatchParameter(name);
1094: }
1095:
1096: /**
1097: *
1098: * @param name
1099: * @return Values of given parameter at the time when item was matched. Parent matched
1100: * parameters are inherited.
1101: */
1102: public String[] getMatchParameters(String name) {
1103: String[] ret = (String[]) matchParameters.get(name);
1104: if (ret != null) {
1105: return ret;
1106: }
1107:
1108: return parent == null ? null : parent.getMatchParameters(name);
1109: }
1110:
1111: /**
1112: * Sets match number in self and parents.
1113: * @param match
1114: */
1115: protected void setLastMatch(int match) {
1116: lastMatch = match;
1117: if (parent != null) {
1118: parent.setLastMatch(match);
1119: }
1120: }
1121:
1122: /**
1123: * @return max of last match of this item and parent item(s)
1124: */
1125: public int getLastMatch() {
1126: return parent == null ? lastMatch : Math.max(lastMatch, parent
1127: .getLastMatch());
1128: }
1129:
1130: public boolean isMatchQueryString() {
1131: return getMatchQueryString();
1132: }
1133:
1134: /**
1135: * @return path to this item.
1136: */
1137: public Menu[] getPath() {
1138: return path;
1139: }
1140:
1141: public Menu getParentMenu() {
1142: return parent;
1143: }
1144:
1145: /**
1146: * @return Root menu
1147: */
1148: public Menu getRoot() {
1149: return parent == null ? this : parent.getRoot();
1150: }
1151:
1152: public Menu getAction(String name) {
1153: return (Menu) actionMap.get(name);
1154: }
1155:
1156: public Menu getChild(String name) {
1157: return (Menu) itemMap.get(name);
1158: }
1159:
1160: public Menu findByXid(String xid) {
1161: if (xid.equals(getXid())) {
1162: return enabled() ? this : null;
1163: }
1164:
1165: if (parent == null) {
1166: Menu ret = (Menu) xidMap.get(xid);
1167: return ret != null && ret.enabled() ? ret : null;
1168: }
1169: return parent.findByXid(xid);
1170: }
1171:
1172: public Menu findById(int id) {
1173: if (getId() == id) {
1174: return enabled() ? this : null;
1175: }
1176:
1177: if (parent == null) {
1178: Menu ret = (Menu) idMap.get(new Integer(id));
1179: return ret != null && ret.enabled() ? ret : null;
1180: }
1181: return parent.findById(id);
1182: }
1183:
1184: public static boolean isBlank(String str) {
1185: return str == null || str.trim().length() == 0;
1186: }
1187:
1188: public static String escapeHtml(String txt) {
1189: if (txt == null) {
1190: return null;
1191: }
1192:
1193: StringBuffer ret = new StringBuffer();
1194: char[] chars = txt.toCharArray();
1195: for (int i = 0; i < chars.length; ++i) {
1196: switch (chars[i]) {
1197: case '<':
1198: ret.append("<");
1199: break;
1200: case '>':
1201: ret.append(">");
1202: break;
1203: case '&':
1204: if (i < chars.length - 1 && '#' == chars[i + 1]) { // Do not double-escape (&#...;)
1205: ret.append(chars[i]);
1206: } else {
1207: ret.append("&");
1208: }
1209: break;
1210: case '\'':
1211: ret.append("'");
1212: break;
1213: case '\\':
1214: ret.append("\");
1215: break;
1216: case '\"':
1217: ret.append(""");
1218: break;
1219: default:
1220: ret.append("&#" + ((int) chars[i]) + ";");
1221: }
1222: }
1223: return ret.toString();
1224: }
1225:
1226: private InjectingClassLoader injectingClassLoader;
1227:
1228: protected InjectingClassLoader getInjectingClassLoader() {
1229: return parent == null ? injectingClassLoader : parent
1230: .getInjectingClassLoader();
1231: }
1232:
1233: private InterfacePool interfacePool;
1234:
1235: protected InterfacePool getInterfacePool() {
1236: return parent == null ? interfacePool : parent
1237: .getInterfacePool();
1238: }
1239:
1240: private Cache cache;
1241:
1242: public Cache getCache() {
1243: return parent == null ? cache : parent.getCache();
1244: }
1245:
1246: private GenerationPolicy generationPolicy;
1247:
1248: public GenerationPolicy getGenerationPolicy() {
1249: return parent == null ? generationPolicy : parent
1250: .getGenerationPolicy();
1251: }
1252:
1253: private Map guidMap;
1254:
1255: public Menu findByGuid(String id) {
1256: Menu ret = (Menu) (parent == null ? guidMap.get(id) : parent
1257: .findByGuid(id));
1258: return ret != null && ret.enabled() ? ret : null;
1259: }
1260:
1261: private void putToGuidMap(String guid, Menu menu) {
1262: if (parent == null) {
1263: guidMap.put(guid, menu);
1264: } else {
1265: parent.putToGuidMap(guid, menu);
1266: }
1267: }
1268:
1269: /**
1270: * This context traces whether there have been null
1271: * returned values.
1272: * @author Pavel
1273: *
1274: */
1275: private static class MissContext implements Context {
1276:
1277: private Context[] masters;
1278: private boolean hadMisses;
1279:
1280: public MissContext(Context[] masters) {
1281: this .masters = masters;
1282: }
1283:
1284: public Object get(String key) {
1285: for (int i = 0; i < masters.length; ++i) {
1286: Object ret = masters[i].get(key);
1287: if (ret != null) {
1288: return ret;
1289: }
1290: }
1291:
1292: hadMisses = true;
1293: return null;
1294: }
1295:
1296: public boolean hadMisses() {
1297: return hadMisses;
1298: }
1299: }
1300:
1301: private MenuNavigatorTemplate getTemplate(String templateName) {
1302: MenuNavigatorTemplate ret = (MenuNavigatorTemplate) navigatorTemplates
1303: .get(templateName);
1304: if (ret == null && parent != null) {
1305: return parent.getTemplate(templateName);
1306: }
1307: return ret;
1308: }
1309:
1310: public Collection getTemplateNames() {
1311: TreeSet ret = new TreeSet();
1312: ret.addAll(navigatorTemplates.keySet());
1313: if (parent != null) {
1314: ret.addAll(parent.getTemplateNames());
1315: }
1316: return ret;
1317: }
1318:
1319: public Object get(String key) {
1320: if (isBlank(key)) {
1321: return null;
1322: }
1323:
1324: int idx = key.indexOf(":");
1325:
1326: if (idx != -1) {
1327: if (idx == key.length() - 1) {
1328: return null;
1329: }
1330:
1331: String nameSpace = key.substring(0, idx);
1332: String name = key.substring(idx + 1);
1333: if ("attribute".equals(nameSpace)) {
1334: return getAttribute(name);
1335: }
1336:
1337: if ("match-parameter".equals(nameSpace)) {
1338: return getMatchParameter(name);
1339: }
1340:
1341: if ("parent".equals(nameSpace)) {
1342: return parent == null ? null : parent.get(name);
1343: }
1344: }
1345:
1346: return super .get(key);
1347: }
1348:
1349: /**
1350: * @return true if there are help topics associated with the menu item.
1351: */
1352: public boolean hasHelp() {
1353: return !helpTopics.isEmpty();
1354: }
1355:
1356: /**
1357: * @return true if this item has sub-items
1358: */
1359: public Collection getChildren() {
1360: return children;
1361: }
1362:
1363: /**
1364: * Chained get
1365: */
1366: public Object get(String key, final Context chain) {
1367:
1368: if (isBlank(key)) {
1369: return null;
1370: }
1371:
1372: MissContext mc = new MissContext(new Context[] { new Context() {
1373:
1374: public Object get(String key) {
1375: return Menu.this .get(key, chain);
1376: }
1377: }, chain });
1378: PropertyParser parser = new PropertyParser(mc, false);
1379:
1380: String contextPath = (String) chain.get("context-path");
1381:
1382: if ("ajax-link".equals(key) || "ajax-label".equals(key)) {
1383: Object divId = chain.get("0");
1384: if (divId == null) {
1385: return "";
1386: }
1387:
1388: String newEmbellishments = "onclick=\"ajax_loadContent('"
1389: + divId + "', this.href); return false;\"";
1390: String embellishments = (String) chain.get("3");
1391: if (embellishments != null) {
1392: newEmbellishments += " " + embellishments;
1393: }
1394:
1395: final String fne = newEmbellishments;
1396:
1397: Context shiftingContex = new Context() {
1398:
1399: public Object get(String key) {
1400: if ("0".equals(key)) {
1401: return chain.get("1");
1402: }
1403: if ("1".equals(key)) {
1404: return chain.get("2");
1405: }
1406: if ("2".equals(key)) {
1407: return fne;
1408: }
1409: return chain.get(key);
1410: }
1411:
1412: };
1413:
1414: return get(key.substring("ajax-".length()), shiftingContex);
1415: }
1416:
1417: if ("link".equals(key)) {
1418: StringBuffer sb = new StringBuffer("<a href=\"");
1419: sb.append(contextPath);
1420: sb.append(getUri());
1421: String queryString = (String) chain.get("1");
1422:
1423: if (!isBlank(queryString)) {
1424: sb.append(sb.indexOf("?") == -1 ? "?" : "&");
1425: sb.append(queryString);
1426: } else if (!isBlank(getQueryTemplate())) {
1427: sb.append(sb.indexOf("?") == -1 ? "?" : "&");
1428: sb.append(parser.parse(getQueryTemplate()));
1429: if (mc.hadMisses()) {
1430: return "";
1431: }
1432: }
1433:
1434: sb.append("\"");
1435: if (!isBlank(getTitle())) {
1436: sb.append(" title=\"");
1437: sb.append(getTitle());
1438: sb.append("\"");
1439: }
1440:
1441: if (!isBlank(getTarget())) {
1442: sb.append(" target=\"");
1443: sb.append(getTarget());
1444: sb.append("\"");
1445: }
1446:
1447: String embellishments = (String) chain.get("2");
1448:
1449: if (!isBlank(embellishments)) {
1450: sb.append(" ");
1451: sb.append(embellishments);
1452: }
1453:
1454: sb.append(">");
1455: String linkText = (String) chain.get("0");
1456: sb.append(escapeHtml(isBlank(linkText) ? getName()
1457: : linkText));
1458: sb.append("</a>");
1459:
1460: return sb.toString();
1461: }
1462:
1463: if ("tooltip-link".equals(key)) {
1464: StringBuffer hrefBuf = new StringBuffer(contextPath);
1465: hrefBuf.append(getUri());
1466: String queryString = (String) chain.get("1");
1467:
1468: if (!isBlank(queryString)) {
1469: hrefBuf.append(hrefBuf.indexOf("?") == -1 ? "?" : "&");
1470: hrefBuf.append(queryString);
1471: } else if (!isBlank(getQueryTemplate())) {
1472: hrefBuf.append(hrefBuf.indexOf("?") == -1 ? "?" : "&");
1473: hrefBuf.append(parser.parse(getQueryTemplate()));
1474: if (mc.hadMisses()) {
1475: return "";
1476: }
1477: }
1478:
1479: StringBuffer sb = new StringBuffer(
1480: "\n<a style=\"display:none\" id=\"xMenuTooltip");
1481: sb.append(getId());
1482: sb.append("\" href=\"");
1483: sb.append(hrefBuf);
1484: if (hrefBuf.indexOf("?") == -1) {
1485: sb.append("?");
1486: } else {
1487: sb.append("&");
1488: }
1489: sb.append("skin=tooltip\"></a>");
1490:
1491: sb.append("<a href=\"");
1492: sb.append(hrefBuf);
1493: sb
1494: .append("\" onmouseover=\"helpTooltip.showTooltip(event,document.getElementById('xMenuTooltip");
1495: sb.append(getId());
1496: sb
1497: .append("').href);return false;\" onmouseout=\"helpTooltip.hideTooltip()\" style=\"border-bottom: 1px dashed gray; text-decoration: none;\" ");
1498:
1499: if (!isBlank(getTarget())) {
1500: sb.append(" target=\"");
1501: sb.append(getTarget());
1502: sb.append("\"");
1503: }
1504:
1505: String embellishments = (String) chain.get("2");
1506: if (!isBlank(embellishments)) {
1507: sb.append(" ");
1508: sb.append(embellishments);
1509: }
1510:
1511: sb.append(">");
1512: String linkText = (String) chain.get("0");
1513: sb.append(escapeHtml(isBlank(linkText) ? getName()
1514: : linkText));
1515: sb.append("</a>");
1516:
1517: return sb.toString();
1518: }
1519:
1520: if ("ajax".equals(key)) {
1521: String divId = (String) chain.get("1");
1522: if (divId == null) {
1523: divId = "xMenuAjaxDiv" + getId();
1524: }
1525: StringBuffer sb = new StringBuffer("<DIV id=\"");
1526: sb.append(divId);
1527: sb.append("\"> </DIV>\n");
1528:
1529: sb.append("<a style=\"display:none\" id=\"");
1530: sb.append(divId);
1531: sb.append("_Anchor\" href=\"");
1532:
1533: sb.append(contextPath);
1534: sb.append(getUri());
1535: String queryString = (String) chain.get("0");
1536:
1537: if (!isBlank(queryString)) {
1538: sb.append(sb.indexOf("?") == -1 ? "?" : "&");
1539: sb.append(queryString);
1540: } else if (!isBlank(getQueryTemplate())) {
1541: sb.append(sb.indexOf("?") == -1 ? "?" : "&");
1542: sb.append(parser.parse(getQueryTemplate()));
1543: if (mc.hadMisses()) {
1544: return "";
1545: }
1546: }
1547:
1548: sb.append("\"> </a>\n");
1549:
1550: sb.append("<script language=\"JavaScript\">\n");
1551: sb.append("ajax_loadContent('");
1552: sb.append(divId);
1553: sb.append("', document.getElementById('");
1554: sb.append(divId);
1555: sb.append("_Anchor').href);\n</script>");
1556: return sb.toString();
1557: }
1558:
1559: if ("label".equals(key)) {
1560: StringBuffer sb = new StringBuffer("<a href=\"");
1561: sb.append(contextPath);
1562: sb.append(getUri());
1563: String queryString = (String) chain.get("1");
1564: String labelText = (String) chain.get("0");
1565: if (!isBlank(queryString)) {
1566: sb.append(sb.indexOf("?") == -1 ? "?" : "&");
1567: sb.append(queryString);
1568: } else if (!isBlank(getQueryTemplate())) {
1569: sb.append(sb.indexOf("?") == -1 ? "?" : "&");
1570: sb.append(parser.parse(getQueryTemplate()));
1571: if (mc.hadMisses()) {
1572: return labelText;
1573: }
1574: }
1575:
1576: sb.append("\"");
1577: if (!isBlank(getTitle())) {
1578: sb.append(" title=\"");
1579: sb.append(getTitle());
1580: sb.append("\"");
1581: }
1582:
1583: if (!isBlank(getTarget())) {
1584: sb.append(" target=\"");
1585: sb.append(getTarget());
1586: sb.append("\"");
1587: }
1588:
1589: String embellishments = (String) chain.get("2");
1590:
1591: if (!isBlank(embellishments)) {
1592: sb.append(" ");
1593: sb.append(embellishments);
1594: }
1595:
1596: sb.append(">");
1597: sb.append(escapeHtml(isBlank(labelText) ? getName()
1598: : labelText));
1599: sb.append("</a>");
1600:
1601: return sb.toString();
1602: }
1603:
1604: if ("tooltip-label".equals(key)) {
1605: StringBuffer hrefBuf = new StringBuffer(contextPath);
1606: hrefBuf.append(getUri());
1607: String queryString = (String) chain.get("1");
1608: String labelText = (String) chain.get("0");
1609:
1610: if (!isBlank(queryString)) {
1611: hrefBuf.append(hrefBuf.indexOf("?") == -1 ? "?" : "&");
1612: hrefBuf.append(queryString);
1613: } else if (!isBlank(getQueryTemplate())) {
1614: hrefBuf.append(hrefBuf.indexOf("?") == -1 ? "?" : "&");
1615: hrefBuf.append(parser.parse(getQueryTemplate()));
1616: if (mc.hadMisses()) {
1617: return labelText;
1618: }
1619: }
1620:
1621: StringBuffer sb = new StringBuffer(
1622: "\n<a style=\"display:none\" id=\"xMenuTooltip");
1623: sb.append(getId());
1624: sb.append("\" href=\"");
1625: sb.append(hrefBuf);
1626: if (hrefBuf.indexOf("?") == -1) {
1627: sb.append("?");
1628: } else {
1629: sb.append("&");
1630: }
1631: sb.append("skin=tooltip\"></a>");
1632:
1633: sb.append("<a href=\"");
1634: sb.append(hrefBuf);
1635: sb
1636: .append("\" onmouseover=\"helpTooltip.showTooltip(event,document.getElementById('xMenuTooltip");
1637: sb.append(getId());
1638: sb
1639: .append("').href);return false;\" onmouseout=\"helpTooltip.hideTooltip()\" style=\"border-bottom: 1px dashed gray; text-decoration: none;\" ");
1640:
1641: if (!isBlank(getTarget())) {
1642: sb.append(" target=\"");
1643: sb.append(getTarget());
1644: sb.append("\"");
1645: }
1646:
1647: String embellishments = (String) chain.get("2");
1648: if (!isBlank(embellishments)) {
1649: sb.append(" ");
1650: sb.append(embellishments);
1651: }
1652:
1653: sb.append(">");
1654: sb.append(escapeHtml(isBlank(labelText) ? getName()
1655: : labelText));
1656: sb.append("</a>");
1657:
1658: return sb.toString();
1659: }
1660:
1661: if ("href".equals(key)) {
1662: if (blankUri) {
1663: return "#";
1664: }
1665:
1666: if (isBlank(getQueryTemplate())) {
1667: return contextPath + getUri();
1668: }
1669:
1670: String ret = contextPath + getUri();
1671: ret += (ret.indexOf("?") == -1 ? "?" : "&")
1672: + parser.parse(getQueryTemplate());
1673: return mc.hadMisses() ? null : ret;
1674: }
1675:
1676: if ("uri".equals(key)) {
1677: return contextPath + getUri();
1678: }
1679:
1680: if ("formHandler".equals(key)) {
1681: String fhu = getFormHandlerUri();
1682: if (fhu == null) {
1683: return null;
1684: }
1685:
1686: return contextPath + fhu;
1687:
1688: }
1689:
1690: if (key.startsWith(TEMPLATE_PREFIX)) {
1691: String templateName = key.substring(TEMPLATE_PREFIX
1692: .length());
1693: Map context = new HashMap();
1694: context.put("menuContext", mc);
1695: context.put("menu", this );
1696: context.put("escaper", new Escaper());
1697: context.put("context-path", chain.get("context-path"));
1698: Object ret = instantiateTemplate(templateName, context);
1699: return mc.hadMisses() ? null : ret;
1700: }
1701:
1702: String menuActionsPath = (String) chain
1703: .get(MENU_HELP_ACTIONS_GLOBAL_PATH);
1704:
1705: if (menuActionsPath != null) {
1706: menuActionsPath = contextPath + menuActionsPath;
1707: }
1708:
1709: String menuHelpId = getHelp(chain).getId();
1710: if ("phelp".equals(key)) { // Help which takes formatting values from parameters
1711: // First parameter - topic path. phelp returns URL of menu item article if this parameter is missing or blank
1712: // Second parameter - topic "attribute". phelp return URL of topic content if this parameter is missing or blank.
1713: // second parameter is ignored if the first one is blank.
1714: if (menuActionsPath == null) {
1715: return null;
1716: }
1717:
1718: String path = (String) chain.get("0");
1719: if (isBlank(path)) {
1720: return menuActionsPath + MENU_HELP_URL_PART
1721: + menuHelpId;
1722: }
1723:
1724: MenuHelpTopicsImpl mht = (MenuHelpTopicsImpl) helpTopics
1725: .get(path);
1726: if (mht == null) {
1727: return null;
1728: }
1729:
1730: String attribute = (String) chain.get("1");
1731: if (isBlank(attribute)) {
1732: return menuActionsPath + TOPIC_CONTENT_URL_PART
1733: + mht.getId();
1734: }
1735:
1736: return mht.get(attribute);
1737: }
1738:
1739: if ("help".equals(key)) { // Templated topic help links
1740: // First parameter - topic path.
1741: // Second parameter - template name. If template name is null then help topic's template name is used
1742: if (menuActionsPath == null) {
1743: return null;
1744: }
1745:
1746: String path = (String) chain.get("0");
1747: if (isBlank(path)) {
1748: return null;
1749: }
1750:
1751: MenuHelpTopicsImpl mht = (MenuHelpTopicsImpl) helpTopics
1752: .get(path);
1753: if (mht == null) {
1754: return null;
1755: }
1756:
1757: String templateName = (String) chain.get("1");
1758: if (isBlank(templateName)) {
1759: templateName = mht.getRenderTemplate();
1760: }
1761: if (isBlank(templateName)) {
1762: return null;
1763: }
1764:
1765: Map context = new HashMap();
1766: context.put("menuContext", mc);
1767: context.put("menu", this );
1768: context.put("escaper", new Escaper());
1769: context.put("topicId", new Integer(mht.getId()));
1770: context.put("context-path", chain.get("context-path"));
1771: context.put("helpURL", menuActionsPath + MENU_HELP_URL_PART
1772: + "T" + mht.getId());
1773: context.put("topicURL", menuActionsPath
1774: + TOPIC_CONTENT_URL_PART + mht.getId());
1775: Object ret = instantiateTemplate(templateName, context);
1776: return mc.hadMisses() ? null : ret;
1777: }
1778:
1779: if ("menuhelp".equals(key)) { // Templated menu help links
1780: // Parameter - template name
1781: if (menuActionsPath == null) {
1782: return null;
1783: }
1784:
1785: String templateName = (String) chain.get("0");
1786: if (isBlank(templateName)) {
1787: return null;
1788: }
1789:
1790: Map context = new HashMap();
1791: context.put("menuContext", mc);
1792: context.put("menu", this );
1793: context.put("escaper", new Escaper());
1794: context.put("context-path", chain.get("context-path"));
1795: context.put("helpURL", menuActionsPath + MENU_HELP_URL_PART
1796: + menuHelpId);
1797: Object ret = instantiateTemplate(templateName, context);
1798: return mc.hadMisses() ? null : ret;
1799: }
1800:
1801: if (key.startsWith("parent:")) {
1802: return parent == null ? null : parent.get(key
1803: .substring("parent:".length()), chain);
1804: }
1805:
1806: // if ("help".equals(key)) {
1807: // String menuActionsPath = (String) rctx.get(MENU_HELP_ACTIONS_GLOBAL_PATH);
1808: // if (menuActionsPath == null) {
1809: // return null;
1810: // }
1811: //
1812: // // URL to action which renders help window with combined topics for menu item.
1813: // return contextPath+menuActionsPath+MENU_HELP_URL_PART+getId();
1814: // } else if (key.startsWith(HELP_PREFIX)) {
1815: // String menuActionsPath = (String) rctx.get(MENU_HELP_ACTIONS_GLOBAL_PATH);
1816: // if (menuActionsPath == null) {
1817: // return null;
1818: // }
1819: //
1820: // menuActionsPath = contextPath + menuActionsPath;
1821: //
1822: // String rest = key.substring(0, HELP_PREFIX.length());
1823: // if (isBlank(rest)) {
1824: // return null;
1825: // }
1826: // int idx = rest.lastIndexOf(":");
1827: // if (idx==-1) { // URL to action which returns topic content.
1828: // MenuHelpTopics mht = (MenuHelpTopics) helpTopics.get(rest);
1829: // return mht==null ? null : menuActionsPath+"topicContent?id="+mht.getId();
1830: // } else { // One of values of MenuHelpTopics
1831: // MenuHelpTopicsImpl mht = (MenuHelpTopicsImpl) helpTopics.get(rest.substring(0, idx));
1832: // String topicKey = idx<rest.length()-1 ? rest.substring(idx+1) : null;
1833: // return mht.get(topicKey);
1834: // }
1835: // } else if (key.startsWith(MENU_ATTRIBUTE)) {
1836: // return getAttribute(key.substring(MENU_ATTRIBUTE.length()+1));
1837: // } else if (key.startsWith(MATCH_PARAMETER)) {
1838: // return getMatchParameter(key.substring(MATCH_PARAMETER.length()+1));
1839: // }
1840: //
1841: // // TODO in the future: interaction things.
1842: //
1843: //
1844: // ChainedContext cc = new ChainedContext(context);
1845: // PropertyParser pp = new PropertyParser(cc, false);
1846: // String ret = pp.parse(mnt.getContent());
1847: // return cc.hadMisses ? null : ret;
1848:
1849: Object ret = get(key);
1850: return ret == null ? chain.get(key) : ret;
1851: }
1852:
1853: /**
1854: * Instantiates template with given context.
1855: * The context must contain "menuContext" entry.
1856: * @param templateName
1857: * @param context
1858: * @return
1859: */
1860: public Object instantiateTemplate(String templateName, Map context) {
1861: MenuNavigatorTemplate mnt = getTemplate(templateName);
1862: if (mnt == null) {
1863: return null;
1864: }
1865:
1866: if (MenuFilter.CT_HTML.equals(mnt.getContentType())) {
1867: PropertyParser pp = new PropertyParser(new MapContext(
1868: context), false);
1869: return pp.parse(mnt.getContent());
1870: }
1871:
1872: Context menuContext = (Context) context.get("menuContext");
1873: EvaluatorFactory eFactory = (EvaluatorFactory) menuContext
1874: .get("global:db/EvaluatorFactory");
1875: if (eFactory == null) {
1876: return null;
1877: }
1878:
1879: try {
1880: EvaluationResult er = eFactory.evaluate(mnt
1881: .getContentType(), mnt.getContent(), "Menu "
1882: + getId() + " template " + mnt.getName(), context,
1883: getInjectingClassLoader());
1884: return er.getOutput();
1885: } catch (HammurapiWebException e) {
1886: logger.error("Template evaluation failed " + mnt.getName()
1887: + ": " + e, e);
1888: return null;
1889: }
1890: }
1891:
1892: /**
1893: * Get with parameters for MenuNavigator to expand ${...|...|...|...} templates.
1894: * @param key Context key
1895: * @param parameters Parameters , addressed by number, e.g. parameter 0 is addressed as ${0}
1896: * @param chain Chain context
1897: * @return key value
1898: */
1899: public Object get(String key, final String[] parameters,
1900: final Context chain) {
1901: final Map parameterMap = new HashMap();
1902: for (int i = 0; parameters != null && i < parameters.length; ++i) {
1903: if (parameters[i] != null) {
1904: parameterMap.put(String.valueOf(i), parameters[i]);
1905: }
1906: }
1907:
1908: return get(key, new Context() {
1909:
1910: public Object get(String key) {
1911: Object ret = parameterMap.get(key);
1912: return ret == null ? chain.get(key) : ret;
1913: }
1914:
1915: });
1916: }
1917:
1918: private Help findHelp(String id, Set idSet, Context context) {
1919: Integer iid = new Integer(getId());
1920: if (idSet.add(iid)) { // To avoid infinite loops.
1921: Help ret = getHelp(context).findHelp(id);
1922: if (ret != null) {
1923: return ret;
1924: }
1925:
1926: Iterator it = children.iterator();
1927: while (it.hasNext()) {
1928: Menu child = (Menu) it.next();
1929: ret = child.findHelp(id, idSet, context);
1930: if (ret != null) {
1931: return ret;
1932: }
1933: }
1934:
1935: if (parent != null) {
1936: return parent.findHelp(id, idSet, context);
1937: }
1938:
1939: }
1940: return null;
1941: }
1942:
1943: public Help findHelp(String id, Context context) {
1944: if (id.startsWith("M")) {
1945: return findById(Integer.parseInt(id.substring(1))).getHelp(
1946: context);
1947: }
1948: return findHelp(id, new HashSet(), context);
1949: }
1950:
1951: /**
1952: * Invoked by Help to navigate menu tree.
1953: * @param term
1954: * @param idSet
1955: * @return
1956: */
1957: Help findTerm(String term, Set idSet, Context context) {
1958: Integer iid = new Integer(getId());
1959: if (idSet.add(iid)) { // To avoid infinite loops.
1960: Help ret = getHelp(context).findTerm(term, idSet, context);
1961: if (ret != null) {
1962: return ret;
1963: }
1964:
1965: Iterator it = children.iterator();
1966: while (it.hasNext()) {
1967: Menu child = (Menu) it.next();
1968: ret = child.findTerm(term, idSet, context);
1969: if (ret != null) {
1970: return ret;
1971: }
1972: }
1973:
1974: if (parent != null) {
1975: return parent.findTerm(term, idSet, context);
1976: }
1977:
1978: }
1979: return null;
1980: }
1981:
1982: private Menu findByPath(String path) {
1983: if (path == null) {
1984: return null;
1985: }
1986:
1987: int idx = path.indexOf('/');
1988: String pathElement = idx == -1 ? path : path.substring(0, idx);
1989: if ("..".equals(pathElement)) {
1990: return getParentMenu();
1991: }
1992:
1993: if (".".equals(pathElement)) {
1994: return this ;
1995: }
1996:
1997: Menu si = getChild(pathElement);
1998:
1999: if (si == null) {
2000: return null;
2001: }
2002:
2003: return idx == -1 ? si : si.findByPath(path.substring(idx + 1));
2004: }
2005:
2006: /**
2007: * Finds menu item by path, XID or GUID.
2008: * @param path
2009: * @return
2010: */
2011: public Menu findItem(String path) {
2012: if ("matched".equals(path)) { // Current item
2013: return this ;
2014: } else if (path.startsWith("path:/")) {
2015: return getRoot().findByPath(
2016: path.substring("path:/".length()));
2017: } else if (path.startsWith("path:")) {
2018: return findByPath(path.substring("path:".length()));
2019: }
2020: if (path.startsWith("id:")) {
2021: return findByXid(path.substring("id:".length()));
2022: } else if (path.startsWith("guid:")) {
2023: return findByGuid(path.substring("guid:".length()));
2024: }
2025:
2026: return null;
2027: }
2028:
2029: private int interactionOffset;
2030:
2031: /**
2032: * Id's for menu items for interactions shall be formed as interaction id + interaction offset + ref offset.
2033: * @return Interaction offset
2034: */
2035: public int getInteractionOffset() {
2036: return Integer.MAX_VALUE / 2; // parent==null ? interactionOffset : parent.getInteractionOffset();
2037: }
2038:
2039: private int nextVirtualId;
2040:
2041: /**
2042: *
2043: * @return Next virtual id number for menu.
2044: */
2045: public int nextVirtualId() {
2046: return parent == null ? nextVirtualId++ : parent
2047: .nextVirtualId();
2048: }
2049:
2050: /**
2051: * evalGuard() sets this thread local variable. If this variable is false then the menu item is disabled for a given request.
2052: * Injector filter takes care of invoking guard evaluation.
2053: */
2054: private ThreadLocal guardTL = new ThreadLocal();
2055:
2056: // private ThreadLocal guardInterpreterTL = new ThreadLocal();
2057:
2058: /**
2059: * @return true if there is no guard or if the guard evaluated to not false for the given thread.
2060: */
2061: public boolean enabled() {
2062: return !Boolean.FALSE.equals(guardTL.get());
2063: }
2064:
2065: public void evalGuard(HttpServletRequest request) {
2066: Object ret = null; // TODO Actually evaluate
2067: if (Boolean.FALSE.equals(ret)) {
2068: guardTL.set(Boolean.FALSE);
2069: } else {
2070: guardTL.set(null);
2071: Iterator it = children.iterator();
2072: while (it.hasNext()) {
2073: Menu child = (Menu) it.next();
2074: child.evalGuard(request);
2075: }
2076: }
2077: }
2078:
2079: }
|