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