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}