/* * JBoss, Home of Professional Open Source * * Distributable under LGPL license. * See terms of license at gnu.org. */ package org.jboss.seam.wiki.core.plugin; import org.jboss.seam.Component; import org.jboss.seam.web.ServletContexts; import org.jboss.seam.international.Messages; import org.jboss.seam.log.Log; import org.jboss.seam.log.Logging; import org.jboss.seam.wiki.core.model.WikiDocument; import org.jboss.seam.wiki.core.model.WikiTextMacro; import org.jboss.seam.wiki.core.plugin.metamodel.MacroPluginModule; import org.jboss.seam.wiki.util.Hash; import java.io.Serializable; import java.util.Map; import java.util.HashMap; import java.util.Date; /** * An instance of a macro in wiki text that has an XHTML include/template. * <p> * Internally it ties the wiki text parsing engine to the plugin and * preferences functionality, as well as template/UI rendering. It is also * the primary API for plugin developers, it's the 'currentMacro' that is * available at all times when writing plugin templates, plugin classes, or * interpolated plugin resources (CSS that contains EL expressions, etc.). * </p> * <p> * It extends a <tt>WikiTextMacro</tt> with additional features that are only * relevant for rendering the macro and its template. * </p> * <p> * A new instance of this class is created for <i>every</i> rendering of wiki * text in the wiki user interface (the visible GUI). You can use the * <tt>attributes</tt> map of this instance to store values that you want to * retrieve again during such rendering. This is especially useful if you * want to avoid generating or loading a value again and again during rendering * of a single piece of wiki text, which can occur if a JSF value binding method * (a getter) is generating/loading a value based on the macro instance. * </p> * <p> * An instance of this class can only be created by the <tt>PluginRegistry</tt>. * * @see PluginRegistry * * @author Christian Bauer */ public class WikiPluginMacro extends WikiTextMacro implements Serializable { private Log log = Logging.getLog(WikiPluginMacro.class); public static final String CURRENT_MACRO_EL_VARIABLE = "currentMacro"; public static final String PAGE_VARIABLE_PREFIX = "macro"; public static final String PAGE_VARIABLE_SEPARATOR = "_"; public static final String EVENT_PREFIX = "Macro."; public static enum CallbackEvent { VIEW_BUILD(".callback.viewBuild"), BEFORE_VIEW_RENDER(".callback.beforeViewRender"), AFTER_VIEW_RENDER(".callback.afterViewRender"); private String name; CallbackEvent(String name) { this.name = name; } public String getName() { return name; } } private String uniqueId = Long.toString(new Date().getTime()); private String clientId; private MacroPluginModule metadata; private Map attributes = new HashMap(); WikiPluginMacro(MacroPluginModule metadata, WikiTextMacro wikiTextMacro) { super(wikiTextMacro); this.metadata = metadata; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public MacroPluginModule getMetadata() { return metadata; } public void setMetadata(MacroPluginModule metadata) { this.metadata = metadata; } public Map getAttributes() { return attributes; } public void setAttributes(Map attributes) { this.attributes = attributes; } // Some convenience methods that generate Strings used all over the place // This needs to be unique _across_ different wiki texts. So the numeric position and the name is not enough, // the hashCode() is overriden in the superclass to be the name/position combination... so we need the uniqueId. // TODO: We can now actually remove the position and name, but they help us with debugging. public String getPageVariableName() { return PAGE_VARIABLE_PREFIX + getPosition() + PAGE_VARIABLE_SEPARATOR + this.uniqueId + PAGE_VARIABLE_SEPARATOR + getName(); } public String getCallbackEventName(CallbackEvent event) { return EVENT_PREFIX+getName()+event.getName(); } public String getRequestImagePath() { return getRequestPathPrefix() + getMetadata().getImagePath(); } public String getRequestStylesheetPath() { return getRequestPathPrefix() + getMetadata().getStylesheetPath(); } private String getRequestPathPrefix() { if (getClientId() == null) { // We need to prefix any request with the currenct web context name if this // is not a JSF request. That means, at this time, simple resource (img, css) // GET requests that use EL which in turn uses instances of this class. return ServletContexts.instance().getRequest().getContextPath(); } return ""; } public String getCacheRegion(String name) { return getMetadata().getQualifiedCacheRegionName(name); } public String getMessage(String message) { return Messages.instance().get(getMetadata().getPlugin().getKey() + "." + message); } /* Cache keys for macros are unique hashes: - unique in all wiki areas: the id of the current document - unique in a particular document: the name and position of the macro in the document - unique with changing macro parameters: the hashcode of any macro parameters - unique for a particular user access level: the current users access level - unique considering the hashCode() of any additional objects passed to the method */ public String getCacheKey() { return getCacheKey(new Object[]{}); } public String getCacheKey(Object o) { return getCacheKey(new Object[]{o}); } public String getCacheKey(Object... objects) { WikiDocument currentDocument = (WikiDocument)Component.getInstance("currentDocument"); Integer accessLevel = (Integer) Component.getInstance("currentAccessLevel"); Hash hash = (Hash)Component.getInstance(Hash.class); log.debug("generating cache key for document: " + currentDocument + " and macro: " + this + " and access level: " + accessLevel); StringBuilder builder = new StringBuilder(); if (log.isDebugEnabled()) log.debug("including id of document: " + currentDocument.getId()); builder.append( currentDocument.getId() ); int namePositionHash = (getName() + "_" + getPosition()).hashCode(); if (log.isDebugEnabled()) log.debug("including name/position of this macro: " + Math.abs(namePositionHash)); builder.append( Math.abs(namePositionHash) ); if (log.isDebugEnabled()) log.debug("including hashCode of macro params: " + Math.abs(getParams().hashCode())); builder.append( Math.abs(getParams().hashCode()) ); if (log.isDebugEnabled()) log.debug("including accessLevel: " + accessLevel); builder.append( accessLevel ); // This needs to be empty-String safe (the additional objects might be some of the // JSF "oh let's map a non-existant request parameter to an empty string" genius behavior... if (objects != null && objects.length > 0) { for (Object o : objects) { if (o != null && Math.abs(o.hashCode()) != 0) { log.debug("including hashCode of object: " + Math.abs(o.hashCode())); builder.append( Math.abs(o.hashCode()) ); } } } return hash.hash(builder.toString()); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; WikiPluginMacro that = (WikiPluginMacro) o; return uniqueId.equals(that.uniqueId); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + uniqueId.hashCode(); return result; } public String toString() { return "WikiPluginMacro UniqueId '" + this.uniqueId + "' (" + getPosition() + "): " + getName() + " Params: " + getParams().size(); } }