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