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}