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;
020
021import org.apache.log4j.Logger;
022import org.apache.wiki.api.engine.FilterManager;
023import org.apache.wiki.api.exceptions.NoSuchVariableException;
024import org.apache.wiki.api.filters.PageFilter;
025import org.apache.wiki.i18n.InternationalizationManager;
026import org.apache.wiki.modules.InternalModule;
027import org.apache.wiki.parser.LinkParsingOperations;
028import org.apache.wiki.preferences.Preferences;
029
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.http.HttpSession;
032import java.lang.reflect.Method;
033import java.security.Principal;
034import java.util.Date;
035import java.util.List;
036import java.util.Properties;
037import java.util.ResourceBundle;
038
039/**
040 *  Manages variables.  Variables are case-insensitive.  A list of all
041 *  available variables is on a Wiki page called "WikiVariables".
042 *
043 *  @since 1.9.20.
044 */
045public class VariableManager
046{
047    private static Logger log = Logger.getLogger( VariableManager.class );
048
049    // FIXME: These are probably obsolete.
050    public static final String VAR_ERROR = "error";
051    public static final String VAR_MSG   = "msg";
052
053    /**
054     *  Contains a list of those properties that shall never be shown.
055     *  Put names here in lower case.
056     */
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 VariableManager( Properties props )
067    {
068    }
069
070    /**
071     *  Parses the link and finds a value.  This is essentially used once
072     *  {@link LinkParsingOperations#isVariableLink(String)} has found that
073     *  the link text actually contains a variable.  For example, you could
074     *  pass in "{$username}" and get back "JanneJalkanen".
075     *
076     *  @param  context The WikiContext
077     *  @param  link    The link text containing the variable name.
078     *  @return The variable value.
079     *  @throws IllegalArgumentException If the format is not valid (does not
080     *          start with "{$", is zero length, etc.)
081     *  @throws NoSuchVariableException If a variable is not known.
082     */
083    public String parseAndGetValue( WikiContext context, String link ) throws IllegalArgumentException, NoSuchVariableException {
084        if( !link.startsWith("{$") ) {
085            throw new IllegalArgumentException( "Link does not start with {$" );
086        }
087        if( !link.endsWith("}") ) {
088            throw new IllegalArgumentException( "Link does not end with }" );
089        }
090        String varName = link.substring(2,link.length()-1);
091
092        return getValue( context, varName.trim() );
093    }
094
095    /**
096     *  This method does in-place expansion of any variables.  However,
097     *  the expansion is not done twice, that is, a variable containing text $variable
098     *  will not be expanded.
099     *  <P>
100     *  The variables should be in the same format ({$variablename} as in the web
101     *  pages.
102     *
103     *  @param context The WikiContext of the current page.
104     *  @param source  The source string.
105     *  @return The source string with variables expanded.
106     */
107    // FIXME: somewhat slow.
108    public String expandVariables( WikiContext context, String source ) {
109        StringBuilder result = new StringBuilder();
110
111        for( int i = 0; i < source.length(); i++ )
112        {
113            if( source.charAt(i) == '{' )
114            {
115                if( i < source.length()-2 && source.charAt(i+1) == '$' )
116                {
117                    int end = source.indexOf( '}', i );
118
119                    if( end != -1 )
120                    {
121                        String varname = source.substring( i+2, end );
122                        String value;
123
124                        try
125                        {
126                            value = getValue( context, varname );
127                        }
128                        catch( NoSuchVariableException e )
129                        {
130                            value = e.getMessage();
131                        }
132                        catch( IllegalArgumentException e )
133                        {
134                            value = e.getMessage();
135                        }
136
137                        result.append( value );
138                        i = end;
139                        continue;
140                    }
141                }
142                else
143                {
144                    result.append( '{' );
145                }
146            }
147            else
148            {
149                result.append( source.charAt(i) );
150            }
151        }
152
153        return result.toString();
154    }
155
156    /**
157     *  Returns the value of a named variable.  See {@link #getValue(WikiContext, String)}.
158     *  The only difference is that this method does not throw an exception, but it
159     *  returns the given default value instead.
160     *
161     *  @param context WikiContext
162     *  @param varName The name of the variable
163     *  @param defValue A default value.
164     *  @return The variable value, or if not found, the default value.
165     */
166    public String getValue( WikiContext context, String varName, String defValue )
167    {
168        try
169        {
170            return getValue( context, varName );
171        }
172        catch( NoSuchVariableException e )
173        {
174            return defValue;
175        }
176    }
177
178    /**
179     *  Returns a value of the named variable.  The resolving order is
180     *  <ol>
181     *    <li>Known "constant" name, such as "pagename", etc.  This is so
182     *        that pages could not override certain constants.
183     *    <li>WikiContext local variable.  This allows a programmer to
184     *        set a parameter which cannot be overridden by user.
185     *    <li>HTTP Session
186     *    <li>HTTP Request parameters
187     *    <li>WikiPage variable.  As set by the user with the SET directive.
188     *    <li>jspwiki.properties
189     *  </ol>
190     *
191     *  Use this method only whenever you really need to have a parameter that
192     *  can be overridden by anyone using the wiki.
193     *
194     *  @param context The WikiContext
195     *  @param varName Name of the variable.
196     *
197     *  @return The variable value.
198     *
199     *  @throws IllegalArgumentException If the name is somehow broken.
200     *  @throws NoSuchVariableException If a variable is not known.
201     */
202    public String getValue( WikiContext context, String varName ) throws IllegalArgumentException, NoSuchVariableException {
203        if( varName == null ) {
204            throw new IllegalArgumentException( "Null variable name." );
205        }
206        if( varName.length() == 0 ) {
207            throw new IllegalArgumentException( "Zero length variable name." );
208        }
209        // Faster than doing equalsIgnoreCase()
210        String name = varName.toLowerCase();
211
212        for( int i = 0; i < THE_BIG_NO_NO_LIST.length; i++ ) {
213            if( name.equals(THE_BIG_NO_NO_LIST[i]) )
214                return ""; // FIXME: Should this be something different?
215        }
216
217        try
218        {
219            //
220            //  Using reflection to get system variables adding a new system variable
221            //  now only involves creating a new method in the SystemVariables class
222            //  with a name starting with get and the first character of the name of
223            //  the variable capitalized. Example:
224            //    public String getMysysvar(){
225            //      return "Hello World";
226            //    }
227            //
228            SystemVariables sysvars = new SystemVariables(context);
229            String methodName = "get"+Character.toUpperCase(name.charAt(0))+name.substring(1);
230            Method method = sysvars.getClass().getMethod(methodName);
231            return (String)method.invoke(sysvars);
232        }
233        catch( NoSuchMethodException e1 )
234        {
235            //
236            //  It is not a system var. Time to handle the other cases.
237            //
238            //  Check if such a context variable exists,
239            //  returning its string representation.
240            //
241            if( (context.getVariable( varName )) != null )
242            {
243                return context.getVariable( varName ).toString();
244            }
245
246            //
247            //  Well, I guess it wasn't a final straw.  We also allow
248            //  variables from the session and the request (in this order).
249            //
250
251            HttpServletRequest req = context.getHttpRequest();
252            if( req != null && req.getSession() != null )
253            {
254                HttpSession session = req.getSession();
255
256                try
257                {
258                    String s;
259
260                    if( (s = (String)session.getAttribute( varName )) != null )
261                        return s;
262
263                    if( (s = context.getHttpParameter( varName )) != null )
264                        return s;
265                }
266                catch( ClassCastException e ) {}
267            }
268
269            //
270            // And the final straw: see if the current page has named metadata.
271            //
272
273            WikiPage pg = context.getPage();
274            if( pg != null )
275            {
276                Object metadata = pg.getAttribute( varName );
277                if( metadata != null )
278                    return metadata.toString();
279            }
280
281            //
282            // And the final straw part 2: see if the "real" current page has
283            // named metadata. This allows a parent page to control a inserted
284            // page through defining variables
285            //
286            WikiPage rpg = context.getRealPage();
287            if( rpg != null )
288            {
289                Object metadata = rpg.getAttribute( varName );
290                if( metadata != null )
291                    return metadata.toString();
292            }
293
294            //
295            // Next-to-final straw: attempt to fetch using property name
296            // We don't allow fetching any other properties than those starting
297            // with "jspwiki.".  I know my own code, but I can't vouch for bugs
298            // in other people's code... :-)
299            //
300
301            if( varName.startsWith("jspwiki.") )
302            {
303                Properties props = context.getEngine().getWikiProperties();
304
305                String s = props.getProperty( varName );
306                if( s != null )
307                {
308                    return s;
309                }
310            }
311
312            //
313            //  Final defaults for some known quantities.
314            //
315
316            if( varName.equals( VAR_ERROR ) || varName.equals( VAR_MSG ) )
317                return "";
318
319            throw new NoSuchVariableException( "No variable "+varName+" defined." );
320        }
321        catch( Exception e )
322        {
323            log.info("Interesting exception: cannot fetch variable value",e);
324        }
325        return "";
326    }
327
328    /**
329     *  This class provides the implementation for the different system variables.
330     *  It is called via Reflection - any access to a variable called $xxx is mapped
331     *  to getXxx() on this class.
332     *  <p>
333     *  This is a lot neater than using a huge if-else if branching structure
334     *  that we used to have before.
335     *  <p>
336     *  Note that since we are case insensitive for variables, and VariableManager
337     *  calls var.toLowerCase(), the getters for the variables do not have
338     *  capitalization anywhere.  This may look a bit odd, but then again, this
339     *  is not meant to be a public class.
340     *
341     *  @since 2.7.0
342     */
343    @SuppressWarnings( "unused" )
344    private static class SystemVariables
345    {
346        private WikiContext m_context;
347
348        public SystemVariables(WikiContext context)
349        {
350            m_context=context;
351        }
352
353        public String getPagename()
354        {
355            return m_context.getPage().getName();
356        }
357
358        public String getApplicationname()
359        {
360            return m_context.getEngine().getApplicationName();
361        }
362
363        public String getJspwikiversion()
364        {
365            return Release.getVersionString();
366        }
367
368        public String getEncoding() {
369            return m_context.getEngine().getContentEncoding().displayName();
370        }
371
372        public String getTotalpages() {
373            return Integer.toString( m_context.getEngine().getPageCount() );
374        }
375
376        public String getPageprovider()
377        {
378            return m_context.getEngine().getCurrentProvider();
379        }
380
381        public String getPageproviderdescription()
382        {
383            return m_context.getEngine().getCurrentProviderInfo();
384        }
385
386        public String getAttachmentprovider() {
387            final WikiProvider p = m_context.getEngine().getAttachmentManager().getCurrentProvider();
388            return (p != null) ? p.getClass().getName() : "-";
389        }
390
391        public String getAttachmentproviderdescription() {
392            final WikiProvider p = m_context.getEngine().getAttachmentManager().getCurrentProvider();
393            return (p != null) ? p.getProviderInfo() : "-";
394        }
395
396        public String getInterwikilinks()
397        {
398            StringBuilder res = new StringBuilder();
399
400            for( String link : m_context.getEngine().getAllInterWikiLinks() )
401            {
402                if( res.length() > 0 ) {
403                    res.append(", ");
404                }
405                res.append( link );
406                res.append( " --> " );
407                res.append( m_context.getEngine().getInterWikiURL(link) );
408            }
409            return res.toString();
410        }
411
412        public String getInlinedimages()
413        {
414            StringBuilder res = new StringBuilder();
415
416            for( String ptrn : m_context.getEngine().getAllInlinedImagePatterns() )
417            {
418                if( res.length() > 0 ) {
419                    res.append(", ");
420                }
421
422                res.append(ptrn);
423            }
424
425            return res.toString();
426        }
427
428        public String getPluginpath()
429        {
430            String s = m_context.getEngine().getPluginManager().getPluginSearchPath();
431
432            return (s == null) ? "-" : s;
433        }
434
435        public String getBaseurl()
436        {
437            return m_context.getEngine().getBaseURL();
438        }
439
440        public String getUptime()
441        {
442            Date now = new Date();
443            long secondsRunning = (now.getTime() - m_context.getEngine().getStartTime().getTime()) / 1000L;
444
445            long seconds = secondsRunning % 60;
446            long minutes = (secondsRunning /= 60) % 60;
447            long hours = (secondsRunning /= 60) % 24;
448            long days = secondsRunning /= 24;
449
450            return days + "d, " + hours + "h " + minutes + "m " + seconds + "s";
451        }
452
453        public String getLoginstatus()
454        {
455            WikiSession session = m_context.getWikiSession();
456            return Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE ).getString( "varmgr." + session.getStatus());
457        }
458
459        public String getUsername()
460        {
461            Principal wup = m_context.getCurrentUser();
462
463            ResourceBundle rb = Preferences.getBundle( m_context, InternationalizationManager.CORE_BUNDLE );
464            return wup != null ? wup.getName() : rb.getString( "varmgr.not.logged.in" );
465        }
466
467        public String getRequestcontext()
468        {
469            return m_context.getRequestContext();
470        }
471
472        public String getPagefilters()
473        {
474            FilterManager fm = m_context.getEngine().getFilterManager();
475            List<PageFilter> filters = fm.getFilterList();
476            StringBuilder sb = new StringBuilder();
477
478            for (PageFilter pf : filters )
479            {
480                String f = pf.getClass().getName();
481
482                if( pf instanceof InternalModule )
483                    continue;
484
485                if( sb.length() > 0 )
486                    sb.append(", ");
487                sb.append(f);
488            }
489
490            return sb.toString();
491        }
492    }
493
494}