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 java.io.IOException;
022import java.text.MessageFormat;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.NoSuchElementException;
026import java.util.ResourceBundle;
027
028import org.apache.log4j.Logger;
029import org.apache.oro.text.regex.MatchResult;
030import org.apache.oro.text.regex.PatternMatcher;
031import org.apache.oro.text.regex.Perl5Matcher;
032import org.apache.wiki.InternalWikiException;
033import org.apache.wiki.WikiContext;
034import org.apache.wiki.WikiEngine;
035import org.apache.wiki.api.engine.PluginManager;
036import org.apache.wiki.api.exceptions.PluginException;
037import org.apache.wiki.api.plugin.ParserStagePlugin;
038import org.apache.wiki.api.plugin.WikiPlugin;
039import org.apache.wiki.preferences.Preferences;
040import org.apache.wiki.render.RenderingManager;
041import org.jdom2.Text;
042
043
044/**
045 * Stores the contents of a plugin in a WikiDocument DOM tree.
046 * <p/>
047 * If the RenderingManager.WYSIWYG_EDITOR_MODE is set to Boolean.TRUE in the context,
048 * then the plugin
049 * is rendered as WikiMarkup.  This allows an HTML editor to work without
050 * rendering the plugin each time as well.
051 * <p/>
052 * If RenderingManager.VAR_EXECUTE_PLUGINS is set to Boolean.FALSE, then
053 * the plugin is not executed.
054 *
055 * @since 2.4
056 */
057public class PluginContent extends Text {
058
059    private static final String BLANK = "";
060    private static final String CMDLINE = "_cmdline";
061    private static final String ELEMENT_BR = "<br/>";
062    private static final String EMITTABLE_PLUGINS = "Image|FormOpen|FormClose|FormInput|FormTextarea|FormSelect";
063    private static final String LINEBREAK = "\n";
064    private static final String PLUGIN_START = "[{";
065    private static final String PLUGIN_END = "}]";
066    private static final String SPACE = " ";
067
068    private static final long serialVersionUID = 1L;
069
070    private static Logger log = Logger.getLogger(PluginContent.class);
071
072    private String m_pluginName;
073    private Map<String, String> m_params;
074
075    /**
076     * Creates a new DOM element with the given plugin name and a map of parameters.
077     *
078     * @param pluginName The FQN of a plugin.
079     * @param parameters A Map of parameters.
080     */
081    public PluginContent(String pluginName, Map<String, String> parameters) {
082        m_pluginName = pluginName;
083        m_params = parameters;
084    }
085
086    /**
087     * Returns the name of the plugin invoked by the DOM element.
088     *
089     * @return Name of the plugin
090     * @since 2.5.7
091     */
092    public String getPluginName() {
093        return m_pluginName;
094    }
095
096    /**
097     * Returns a parameter value from the parameter map.
098     *
099     * @param name the name of the parameter.
100     * @return The value from the map, or null, if no such parameter exists.
101     */
102    public String getParameter(String name) {
103        return m_params.get(name);
104    }
105
106    /**
107     * Returns the parameter map given in the constructor.
108     *
109     * @return The parameter map.
110     */
111    public Map<String, String> getParameters() {
112        return m_params;
113    }
114
115    /**
116     * Returns the rendered plugin.  Only calls getText().
117     *
118     * @return HTML
119     */
120    public String getValue() {
121        return getText();
122    }
123
124    /**
125     * The main invocation for the plugin.  When the getText() is called, it
126     * invokes the plugin and returns its contents.  If there is no Document
127     * yet, only returns the plugin name itself.
128     *
129     * @return The plugin rendered according to the options set in the WikiContext.
130     */
131    public String getText() {
132        WikiDocument doc = ( WikiDocument )getDocument();
133
134        if (doc == null) {
135            //
136            // This element has not yet been attached anywhere, so we simply assume there is
137            // no rendering and return the plugin name.  This is required e.g. when the 
138            // paragraphify() checks whether the element is empty or not.  We can't of course
139            // know whether the rendering would result in an empty string or not, but let us
140            // assume it does not.
141            //
142
143            return getPluginName();
144        }
145
146        WikiContext context = doc.getContext();
147        
148        if( context == null ) {
149            log.info( "WikiContext garbage-collected, cannot proceed" );
150            return getPluginName();
151        }
152
153        return invoke( context );
154    }
155
156    /**
157     * Performs plugin invocation and return its contents.
158     * 
159     * @param context WikiContext in which the plugin is executed. Must NOT be null.
160     * @return plugin contents.
161     */
162    public String invoke( WikiContext context ) {
163        String result;
164        Boolean wysiwygVariable = (Boolean) context.getVariable(RenderingManager.WYSIWYG_EDITOR_MODE);
165        boolean wysiwygEditorMode = false;
166        if (wysiwygVariable != null) {
167            wysiwygEditorMode = wysiwygVariable.booleanValue();
168        }
169
170        try {
171            //
172            //  Determine whether we should emit the actual code for this plugin or
173            //  whether we should execute it.  For some plugins we always execute it,
174            //  since they can be edited visually.
175            //
176            // FIXME: The plugin name matching should not be done here, but in a per-editor resource
177            if (wysiwygEditorMode && !m_pluginName.matches(EMITTABLE_PLUGINS)) {
178                result = PLUGIN_START + m_pluginName + SPACE;
179
180                // convert newlines to <br> in case the plugin has a body.
181                String cmdLine = (m_params.get(CMDLINE)).replaceAll(LINEBREAK, ELEMENT_BR);
182
183                result = result + cmdLine + PLUGIN_END;
184            } else {
185                Boolean b = (Boolean) context.getVariable(RenderingManager.VAR_EXECUTE_PLUGINS);
186                if (b != null && !b.booleanValue()) {
187                    return BLANK;
188                }
189
190                WikiEngine engine = context.getEngine();
191
192                Map<String, String> parsedParams = new HashMap<String, String>();
193
194                //
195                //  Parse any variable instances from the string
196                //
197                for (Map.Entry<String, String> e : m_params.entrySet()) {
198                    String val = e.getValue();
199                    val = engine.getVariableManager().expandVariables(context, val);
200                    parsedParams.put(e.getKey(), val);
201                }
202                PluginManager pm = engine.getPluginManager();
203                result = pm.execute(context, m_pluginName, parsedParams);
204            }
205        } catch (Exception e) {
206            if (wysiwygEditorMode) {
207                result = "";
208            } else {
209                // log.info("Failed to execute plugin",e);
210                ResourceBundle rb = Preferences.getBundle(context, WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
211                result = MarkupParser.makeError( MessageFormat.format( rb.getString( "plugin.error.insertionfailed" ), 
212                                                                       context.getRealPage().getWiki(), 
213                                                                       context.getRealPage().getName(), 
214                                                                       e.getMessage() ) ).getText();
215            }
216        }
217
218
219        return result;
220    }
221
222    /**
223     * Executes the executeParse() method.
224     *
225     * @param context The WikiContext
226     * @throws PluginException If something goes wrong.
227     */
228    public void executeParse(WikiContext context) throws PluginException {
229        PluginManager pm = context.getEngine().getPluginManager();
230        if (pm.pluginsEnabled()) {
231            ResourceBundle rb = Preferences.getBundle(context, WikiPlugin.CORE_PLUGINS_RESOURCEBUNDLE);
232            Map<String, String> params = getParameters();
233            WikiPlugin plugin = pm.newWikiPlugin(getPluginName(), rb);
234            try {
235                if (plugin != null && plugin instanceof ParserStagePlugin) {
236                    ((ParserStagePlugin) plugin).executeParser(this, context, params);
237                }
238            } catch (ClassCastException e) {
239                throw new PluginException(MessageFormat.format(rb.getString("plugin.error.notawikiplugin"), getPluginName()), e);
240            }
241        }
242    }
243
244    /**
245     * Parses a plugin invocation and returns a DOM element.
246     *
247     * @param context     The WikiContext
248     * @param commandline The line to parse
249     * @param pos         The position in the stream parsing.
250     * @return A DOM element
251     * @throws PluginException If plugin invocation is faulty
252     * @since 2.10.0
253     */
254    public static PluginContent parsePluginLine(WikiContext context, String commandline, int pos) throws PluginException {
255        PatternMatcher matcher = new Perl5Matcher();
256
257        try {
258            PluginManager pm = context.getEngine().getPluginManager();
259            if (matcher.contains(commandline, pm.getPluginPattern())) {
260
261                MatchResult res = matcher.getMatch();
262
263                String plugin = res.group(2);
264                String args = commandline.substring(res.endOffset(0),
265                        commandline.length() -
266                                (commandline.charAt(commandline.length() - 1) == '}' ? 1 : 0));
267                Map<String, String> arglist = pm.parseArgs(args);
268
269                // set wikitext bounds of plugin as '_bounds' parameter, e.g., [345,396]
270                if (pos != -1) {
271                    int end = pos + commandline.length() + 2;
272                    String bounds = pos + "|" + end;
273                    arglist.put(PluginManager.PARAM_BOUNDS, bounds);
274                }
275
276                PluginContent result = new PluginContent(plugin, arglist);
277
278                return result;
279            }
280        } catch (ClassCastException e) {
281            log.error("Invalid type offered in parsing plugin arguments.", e);
282            throw new InternalWikiException("Oops, someone offered !String!", e);
283        } catch (NoSuchElementException e) {
284            String msg = "Missing parameter in plugin definition: " + commandline;
285            log.warn(msg, e);
286            throw new PluginException(msg);
287        } catch (IOException e) {
288            String msg = "Zyrf.  Problems with parsing arguments: " + commandline;
289            log.warn(msg, e);
290            throw new PluginException(msg);
291        }
292
293        return null;
294    }
295
296}