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     */
019    package org.apache.wiki;
020    
021    import java.lang.reflect.Method;
022    import java.security.Principal;
023    import java.util.Date;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Properties;
027    import java.util.ResourceBundle;
028    
029    import javax.servlet.http.HttpServletRequest;
030    import javax.servlet.http.HttpSession;
031    
032    import org.apache.log4j.Logger;
033    import org.apache.wiki.api.engine.FilterManager;
034    import org.apache.wiki.api.exceptions.NoSuchVariableException;
035    import org.apache.wiki.api.filters.PageFilter;
036    import org.apache.wiki.i18n.InternationalizationManager;
037    import org.apache.wiki.modules.InternalModule;
038    import 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     */
046    public 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            StringBuffer result = new StringBuffer();
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                StringBuffer res = new StringBuffer();
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                StringBuffer res = new StringBuffer();
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                StringBuffer sb = new StringBuffer();
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    }