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}