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