001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018*/ 019package org.apache.wiki.parser; 020 021import org.apache.log4j.Logger; 022import org.apache.oro.text.regex.MatchResult; 023import org.apache.oro.text.regex.PatternMatcher; 024import org.apache.oro.text.regex.Perl5Matcher; 025import org.apache.wiki.InternalWikiException; 026import org.apache.wiki.api.core.Context; 027import org.apache.wiki.api.core.Engine; 028import org.apache.wiki.api.exceptions.PluginException; 029import org.apache.wiki.api.plugin.ParserStagePlugin; 030import org.apache.wiki.api.plugin.Plugin; 031import org.apache.wiki.api.plugin.PluginElement; 032import org.apache.wiki.plugin.PluginManager; 033import org.apache.wiki.preferences.Preferences; 034import org.apache.wiki.variables.VariableManager; 035import org.jdom2.Text; 036 037import java.io.IOException; 038import java.text.MessageFormat; 039import java.util.HashMap; 040import java.util.Map; 041import java.util.NoSuchElementException; 042import java.util.ResourceBundle; 043 044 045/** 046 * Stores the contents of a plugin in a WikiDocument DOM tree. 047 * <p/> 048 * If the Context.VAR_WYSIWYG_EDITOR_MODE is set to Boolean.TRUE in the context, then the plugin is rendered as WikiMarkup. 049 * This allows an HTML editor to work without rendering the plugin each time as well. 050 * <p/> 051 * If Context.VAR_EXECUTE_PLUGINS is set to Boolean.FALSE, then the plugin is not executed. 052 * 053 * @since 2.4 054 */ 055public class PluginContent extends Text implements PluginElement { 056 057 private static final String BLANK = ""; 058 private static final String CMDLINE = "_cmdline"; 059 private static final String ELEMENT_BR = "<br/>"; 060 private static final String EMITTABLE_PLUGINS = "Image|FormOpen|FormClose|FormInput|FormTextarea|FormSelect"; 061 private static final String LINEBREAK = "\n"; 062 private static final String PLUGIN_START = "[{"; 063 private static final String PLUGIN_END = "}]"; 064 private static final String SPACE = " "; 065 066 private static final long serialVersionUID = 1L; 067 private static final Logger log = Logger.getLogger(PluginContent.class); 068 069 private String m_pluginName; 070 private Map< String, String > m_params; 071 072 /** 073 * Creates a new DOM element with the given plugin name and a map of parameters. 074 * 075 * @param pluginName The FQN of a plugin. 076 * @param parameters A Map of parameters. 077 */ 078 public PluginContent( final String pluginName, final Map< String, String > parameters) { 079 m_pluginName = pluginName; 080 m_params = parameters; 081 } 082 083 /**{@inheritDoc}*/ 084 @Override 085 public String getPluginName() { 086 return m_pluginName; 087 } 088 089 /**{@inheritDoc}*/ 090 @Override 091 public String getParameter( final String name) { 092 return m_params.get( name ); 093 } 094 095 /**{@inheritDoc}*/ 096 @Override 097 public Map< String, String > getParameters() { 098 return m_params; 099 } 100 101 /**{@inheritDoc}*/ 102 @Override 103 public String getValue() { 104 return getText(); 105 } 106 107 /**{@inheritDoc}*/ 108 @Override 109 public String getText() { 110 final WikiDocument doc = ( WikiDocument )getDocument(); 111 if( doc == null ) { 112 // 113 // This element has not yet been attached anywhere, so we simply assume there is no rendering and return the plugin name. 114 // This is required e.g. when the paragraphify() checks whether the element is empty or not. We can't of course know 115 // whether the rendering would result in an empty string or not, but let us assume it does not. 116 // 117 return getPluginName(); 118 } 119 120 final Context context = doc.getContext(); 121 if( context == null ) { 122 log.info( "WikiContext garbage-collected, cannot proceed" ); 123 return getPluginName(); 124 } 125 126 return invoke( context ); 127 } 128 129 /**{@inheritDoc}*/ 130 @Override 131 public String invoke( final Context context ) { 132 String result; 133 final Boolean wysiwygVariable = context.getVariable( Context.VAR_WYSIWYG_EDITOR_MODE ); 134 boolean wysiwygEditorMode = false; 135 if( wysiwygVariable != null ) { 136 wysiwygEditorMode = wysiwygVariable; 137 } 138 139 try { 140 // 141 // Determine whether we should emit the actual code for this plugin or 142 // whether we should execute it. For some plugins we always execute it, 143 // since they can be edited visually. 144 // 145 // FIXME: The plugin name matching should not be done here, but in a per-editor resource 146 if( wysiwygEditorMode && !m_pluginName.matches( EMITTABLE_PLUGINS ) ) { 147 result = PLUGIN_START + m_pluginName + SPACE; 148 149 // convert newlines to <br> in case the plugin has a body. 150 final String cmdLine = m_params.get( CMDLINE ).replaceAll( LINEBREAK, ELEMENT_BR ); 151 result = result + cmdLine + PLUGIN_END; 152 } else { 153 final Boolean b = context.getVariable( Context.VAR_EXECUTE_PLUGINS ); 154 if (b != null && !b ) { 155 return BLANK; 156 } 157 158 final Engine engine = context.getEngine(); 159 final Map< String, String > parsedParams = new HashMap<>(); 160 161 // Parse any variable instances from the string 162 for( final Map.Entry< String, String > e : m_params.entrySet() ) { 163 String val = e.getValue(); 164 val = engine.getManager( VariableManager.class).expandVariables( context, val ); 165 parsedParams.put( e.getKey(), val ); 166 } 167 final PluginManager pm = engine.getManager( PluginManager.class ); 168 result = pm.execute( context, m_pluginName, parsedParams ); 169 } 170 } catch( final Exception e ) { 171 if( wysiwygEditorMode ) { 172 result = ""; 173 } else { 174 // log.info("Failed to execute plugin",e); 175 final ResourceBundle rb = Preferences.getBundle( context, Plugin.CORE_PLUGINS_RESOURCEBUNDLE ); 176 result = MarkupParser.makeError( MessageFormat.format( rb.getString( "plugin.error.insertionfailed" ), 177 context.getRealPage().getWiki(), 178 context.getRealPage().getName(), 179 e.getMessage() ) ).getText(); 180 } 181 } 182 183 return result; 184 } 185 186 /**{@inheritDoc}*/ 187 @Override 188 public void executeParse( final Context context ) throws PluginException { 189 final PluginManager pm = context.getEngine().getManager( PluginManager.class ); 190 if( pm.pluginsEnabled() ) { 191 final ResourceBundle rb = Preferences.getBundle( context, Plugin.CORE_PLUGINS_RESOURCEBUNDLE); 192 final Map< String, String > params = getParameters(); 193 final Plugin plugin = pm.newWikiPlugin( getPluginName(), rb ); 194 try { 195 if( plugin instanceof ParserStagePlugin ) { 196 ( ( ParserStagePlugin )plugin ).executeParser(this, context, params ); 197 } 198 } catch( final ClassCastException e ) { 199 throw new PluginException( MessageFormat.format( rb.getString("plugin.error.notawikiplugin"), getPluginName() ), e ); 200 } 201 } 202 } 203 204 /** 205 * Parses a plugin invocation and returns a DOM element. 206 * 207 * @param context The WikiContext 208 * @param commandline The line to parse 209 * @param pos The position in the stream parsing. 210 * @return A DOM element 211 * @throws PluginException If plugin invocation is faulty 212 * @since 2.10.0 213 */ 214 public static PluginContent parsePluginLine( final Context context, final String commandline, final int pos ) throws PluginException { 215 final PatternMatcher matcher = new Perl5Matcher(); 216 217 try { 218 final PluginManager pm = context.getEngine().getManager( PluginManager.class ); 219 if( matcher.contains( commandline, pm.getPluginPattern() ) ) { 220 final MatchResult res = matcher.getMatch(); 221 final String plugin = res.group( 2 ); 222 final String args = commandline.substring( res.endOffset( 0 ), 223 commandline.length() - ( commandline.charAt( commandline.length() - 1 ) == '}' ? 1 : 0 ) ); 224 final Map< String, String > arglist = pm.parseArgs( args ); 225 226 // set wikitext bounds of plugin as '_bounds' parameter, e.g., [345,396] 227 if( pos != -1 ) { 228 final int end = pos + commandline.length() + 2; 229 final String bounds = pos + "|" + end; 230 arglist.put( PluginManager.PARAM_BOUNDS, bounds ); 231 } 232 233 return new PluginContent( plugin, arglist ); 234 } 235 } catch( final ClassCastException e ) { 236 log.error( "Invalid type offered in parsing plugin arguments.", e ); 237 throw new InternalWikiException( "Oops, someone offered !String!", e ); 238 } catch( final NoSuchElementException e ) { 239 final String msg = "Missing parameter in plugin definition: " + commandline; 240 log.warn( msg, e ); 241 throw new PluginException( msg ); 242 } catch( final IOException e ) { 243 final String msg = "Zyrf. Problems with parsing arguments: " + commandline; 244 log.warn( msg, e ); 245 throw new PluginException( msg ); 246 } 247 248 return null; 249 } 250 251}