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.variables;
020
021import org.apache.log4j.Logger;
022import org.apache.wiki.api.Release;
023import org.apache.wiki.api.core.Context;
024import org.apache.wiki.api.core.Page;
025import org.apache.wiki.api.core.Session;
026import org.apache.wiki.api.exceptions.NoSuchVariableException;
027import org.apache.wiki.api.filters.PageFilter;
028import org.apache.wiki.api.providers.WikiProvider;
029import org.apache.wiki.attachment.AttachmentManager;
030import org.apache.wiki.filters.FilterManager;
031import org.apache.wiki.i18n.InternationalizationManager;
032import org.apache.wiki.modules.InternalModule;
033import org.apache.wiki.pages.PageManager;
034import org.apache.wiki.preferences.Preferences;
035
036import javax.servlet.http.HttpServletRequest;
037import javax.servlet.http.HttpSession;
038import java.lang.reflect.Method;
039import java.security.Principal;
040import java.util.Date;
041import java.util.List;
042import java.util.Properties;
043import java.util.ResourceBundle;
044
045
046/**
047 *  Manages variables.  Variables are case-insensitive.  A list of all available variables is on a Wiki page called "WikiVariables".
048 *
049 *  @since 1.9.20.
050 */
051public class DefaultVariableManager implements VariableManager {
052
053    private static final Logger log = Logger.getLogger( DefaultVariableManager.class );
054
055    /**
056     *  Contains a list of those properties that shall never be shown. Put names here in lower case.
057     */
058    static final String[] THE_BIG_NO_NO_LIST = {
059        "jspwiki.auth.masterpassword"
060    };
061
062    /**
063     *  Creates a VariableManager object using the property list given.
064     *  @param props The properties.
065     */
066    public DefaultVariableManager( final Properties props ) {
067    }
068
069    /**
070     *  {@inheritDoc}
071     */
072    @Override
073    public String parseAndGetValue( final Context context, final String link ) throws IllegalArgumentException, NoSuchVariableException {
074        if( !link.startsWith( "{$" ) ) {
075            throw new IllegalArgumentException( "Link does not start with {$" );
076        }
077        if( !link.endsWith( "}" ) ) {
078            throw new IllegalArgumentException( "Link does not end with }" );
079        }
080        final String varName = link.substring( 2, link.length() - 1 );
081
082        return getValue( context, varName.trim() );
083    }
084
085    /**
086     *  {@inheritDoc}
087     */
088    @Override
089    // FIXME: somewhat slow.
090    public String expandVariables( final Context context, final String source ) {
091        final StringBuilder result = new StringBuilder();
092        for( int i = 0; i < source.length(); i++ ) {
093            if( source.charAt(i) == '{' ) {
094                if( i < source.length()-2 && source.charAt(i+1) == '$' ) {
095                    final int end = source.indexOf( '}', i );
096
097                    if( end != -1 ) {
098                        final String varname = source.substring( i+2, end );
099                        String value;
100
101                        try {
102                            value = getValue( context, varname );
103                        } catch( final NoSuchVariableException | IllegalArgumentException e ) {
104                            value = e.getMessage();
105                        }
106
107                        result.append( value );
108                        i = end;
109                    }
110                } else {
111                    result.append( '{' );
112                }
113            } else {
114                result.append( source.charAt(i) );
115            }
116        }
117
118        return result.toString();
119    }
120
121    /**
122     *  {@inheritDoc}
123     */
124    @Override
125    public String getValue( final Context context, final String varName, final String defValue ) {
126        try {
127            return getValue( context, varName );
128        } catch( final NoSuchVariableException e ) {
129            return defValue;
130        }
131    }
132
133    /**
134     *  {@inheritDoc}
135     */
136    @Override
137    public String getVariable( final Context context, final String name ) {
138        return getValue( context, name, null );
139    }
140
141    /**
142     *  {@inheritDoc}
143     */
144    @Override
145    public String getValue( final Context context, final String varName ) throws IllegalArgumentException, NoSuchVariableException {
146        if( varName == null ) {
147            throw new IllegalArgumentException( "Null variable name." );
148        }
149        if( varName.length() == 0 ) {
150            throw new IllegalArgumentException( "Zero length variable name." );
151        }
152        // Faster than doing equalsIgnoreCase()
153        final String name = varName.toLowerCase();
154
155        for( final String value : THE_BIG_NO_NO_LIST ) {
156            if( name.equals( value ) ) {
157                return ""; // FIXME: Should this be something different?
158            }
159        }
160
161        try {
162            //
163            //  Using reflection to get system variables adding a new system variable
164            //  now only involves creating a new method in the SystemVariables class
165            //  with a name starting with get and the first character of the name of
166            //  the variable capitalized. Example:
167            //    public String getMysysvar(){
168            //      return "Hello World";
169            //    }
170            //
171            final SystemVariables sysvars = new SystemVariables( context );
172            final String methodName = "get" + Character.toUpperCase( name.charAt( 0 ) ) + name.substring( 1 );
173            final Method method = sysvars.getClass().getMethod( methodName );
174            return ( String )method.invoke( sysvars );
175        } catch( final NoSuchMethodException e1 ) {
176            //
177            //  It is not a system var. Time to handle the other cases.
178            //
179            //  Check if such a context variable exists, returning its string representation.
180            //
181            if( ( context.getVariable( varName ) ) != null ) {
182                return context.getVariable( varName ).toString();
183            }
184
185            //
186            //  Well, I guess it wasn't a final straw.  We also allow variables from the session and the request (in this order).
187            //
188            final HttpServletRequest req = context.getHttpRequest();
189            if( req != null && req.getSession() != null ) {
190                final HttpSession session = req.getSession();
191
192                try {
193                    String s = ( String )session.getAttribute( varName );
194
195                    if( s != null ) {
196                        return s;
197                    }
198
199                    s = context.getHttpParameter( varName );
200                    if( s != null ) {
201                        return s;
202                    }
203                } catch( final ClassCastException e ) {
204                    log.debug( "Not a String: " + varName );
205                }
206            }
207
208            //
209            // And the final straw: see if the current page has named metadata.
210            //
211            final Page pg = context.getPage();
212            if( pg != null ) {
213                final Object metadata = pg.getAttribute( varName );
214                if( metadata != null ) {
215                    return metadata.toString();
216                }
217            }
218
219            //
220            // And the final straw part 2: see if the "real" current page has named metadata. This allows
221            // a parent page to control a inserted page through defining variables
222            //
223            final Page rpg = context.getRealPage();
224            if( rpg != null ) {
225                final Object metadata = rpg.getAttribute( varName );
226                if( metadata != null ) {
227                    return metadata.toString();
228                }
229            }
230
231            //
232            // Next-to-final straw: attempt to fetch using property name. We don't allow fetching any other
233            // properties than those starting with "jspwiki.".  I know my own code, but I can't vouch for bugs
234            // in other people's code... :-)
235            //
236            if( varName.startsWith("jspwiki.") ) {
237                final Properties props = context.getEngine().getWikiProperties();
238                final String s = props.getProperty( varName );
239                if( s != null ) {
240                    return s;
241                }
242            }
243
244            //
245            //  Final defaults for some known quantities.
246            //
247            if( varName.equals( VAR_ERROR ) || varName.equals( VAR_MSG ) ) {
248                return "";
249            }
250
251            throw new NoSuchVariableException( "No variable " + varName + " defined." );
252        } catch( final Exception e ) {
253            log.info("Interesting exception: cannot fetch variable value", e );
254        }
255        return "";
256    }
257
258    /**
259     *  This class provides the implementation for the different system variables.
260     *  It is called via Reflection - any access to a variable called $xxx is mapped
261     *  to getXxx() on this class.
262     *  <p>
263     *  This is a lot neater than using a huge if-else if branching structure
264     *  that we used to have before.
265     *  <p>
266     *  Note that since we are case insensitive for variables, and VariableManager
267     *  calls var.toLowerCase(), the getters for the variables do not have
268     *  capitalization anywhere.  This may look a bit odd, but then again, this
269     *  is not meant to be a public class.
270     *
271     *  @since 2.7.0
272     */
273    @SuppressWarnings( "unused" )
274    private static class SystemVariables {
275
276        private final Context m_context;
277
278        public SystemVariables( final Context context )
279        {
280            m_context=context;
281        }
282
283        public String getPagename()
284        {
285            return m_context.getPage().getName();
286        }
287
288        public String getApplicationname()
289        {
290            return m_context.getEngine().getApplicationName();
291        }
292
293        public String getJspwikiversion()
294        {
295            return Release.getVersionString();
296        }
297
298        public String getEncoding() {
299            return m_context.getEngine().getContentEncoding().displayName();
300        }
301
302        public String getTotalpages() {
303            return Integer.toString( m_context.getEngine().getManager( PageManager.class ).getTotalPageCount() );
304        }
305
306        public String getPageprovider() {
307            return m_context.getEngine().getManager( PageManager.class ).getCurrentProvider();
308        }
309
310        public String getPageproviderdescription() {
311            return m_context.getEngine().getManager( PageManager.class ).getProviderDescription();
312        }
313
314        public String getAttachmentprovider() {
315            final WikiProvider p = m_context.getEngine().getManager( AttachmentManager.class ).getCurrentProvider();
316            return (p != null) ? p.getClass().getName() : "-";
317        }
318
319        public String getAttachmentproviderdescription() {
320            final WikiProvider p = m_context.getEngine().getManager( AttachmentManager.class ).getCurrentProvider();
321            return (p != null) ? p.getProviderInfo() : "-";
322        }
323
324        public String getInterwikilinks() {
325            final StringBuilder res = new StringBuilder();
326
327            for( final String link : m_context.getEngine().getAllInterWikiLinks() ) {
328                if( res.length() > 0 ) {
329                    res.append( ", " );
330                }
331                res.append( link );
332                res.append( " --> " );
333                res.append( m_context.getEngine().getInterWikiURL( link ) );
334            }
335            return res.toString();
336        }
337
338        public String getInlinedimages() {
339            final StringBuilder res = new StringBuilder();
340            for( final String ptrn : m_context.getEngine().getAllInlinedImagePatterns() ) {
341                if( res.length() > 0 ) {
342                    res.append( ", " );
343                }
344
345                res.append( ptrn );
346            }
347
348            return res.toString();
349        }
350
351        public String getPluginpath() {
352            final String s = m_context.getEngine().getPluginSearchPath();
353
354            return ( s == null ) ? "-" : s;
355        }
356
357        public String getBaseurl()
358        {
359            return m_context.getEngine().getBaseURL();
360        }
361
362        public String getUptime() {
363            final Date now = new Date();
364            long secondsRunning = ( now.getTime() - m_context.getEngine().getStartTime().getTime() ) / 1_000L;
365
366            final long seconds = secondsRunning % 60;
367            final long minutes = (secondsRunning /= 60) % 60;
368            final long hours = (secondsRunning /= 60) % 24;
369            final long days = secondsRunning /= 24;
370
371            return days + "d, " + hours + "h " + minutes + "m " + seconds + "s";
372        }
373
374        public String getLoginstatus() {
375            final Session session = m_context.getWikiSession();
376            return Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE ).getString( "varmgr." + session.getStatus() );
377        }
378
379        public String getUsername() {
380            final Principal wup = m_context.getCurrentUser();
381            final ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE );
382            return wup != null ? wup.getName() : rb.getString( "varmgr.not.logged.in" );
383        }
384
385        public String getRequestcontext()
386        {
387            return m_context.getRequestContext();
388        }
389
390        public String getPagefilters() {
391            final FilterManager fm = m_context.getEngine().getManager( FilterManager.class );
392            final List< PageFilter > filters = fm.getFilterList();
393            final StringBuilder sb = new StringBuilder();
394            for( final PageFilter pf : filters ) {
395                final String f = pf.getClass().getName();
396                if( pf instanceof InternalModule ) {
397                    continue;
398                }
399
400                if( sb.length() > 0 ) {
401                    sb.append( ", " );
402                }
403                sb.append( f );
404            }
405            return sb.toString();
406        }
407    }
408
409}