001 package org.apache.commons.ognl; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import org.apache.commons.ognl.enhance.LocalReference; 023 024 import java.util.Collection; 025 import java.util.HashMap; 026 import java.util.HashSet; 027 import java.util.LinkedHashMap; 028 import java.util.Map; 029 import java.util.Set; 030 import java.util.Stack; 031 032 /** 033 * This class defines the execution context for an OGNL expression 034 * 035 * @author Luke Blanshard (blanshlu@netscape.net) 036 * @author Drew Davidson (drew@ognl.org) 037 */ 038 public class OgnlContext 039 implements Map<String, Object> 040 { 041 042 public static final String CONTEXT_CONTEXT_KEY = "context"; 043 044 public static final String ROOT_CONTEXT_KEY = "root"; 045 046 public static final String THIS_CONTEXT_KEY = "this"; 047 048 public static final String TRACE_EVALUATIONS_CONTEXT_KEY = "_traceEvaluations"; 049 050 public static final String LAST_EVALUATION_CONTEXT_KEY = "_lastEvaluation"; 051 052 public static final String KEEP_LAST_EVALUATION_CONTEXT_KEY = "_keepLastEvaluation"; 053 054 public static final String CLASS_RESOLVER_CONTEXT_KEY = "_classResolver"; 055 056 public static final String TYPE_CONVERTER_CONTEXT_KEY = "_typeConverter"; 057 058 public static final String MEMBER_ACCESS_CONTEXT_KEY = "_memberAccess"; 059 060 private static final String PROPERTY_KEY_PREFIX = "ognl"; 061 062 private static boolean defaultTraceEvaluations = false; 063 064 private static boolean defaultKeepLastEvaluation = false; 065 066 public static final DefaultClassResolver DEFAULT_CLASS_RESOLVER = new DefaultClassResolver(); 067 068 public static final TypeConverter DEFAULT_TYPE_CONVERTER = new DefaultTypeConverter(); 069 070 public static final MemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess( false ); 071 072 private static final Set<String> RESERVED_KEYS = new HashSet<String>( 11 ); 073 074 private Object root; 075 076 private Object currentObject; 077 078 private Node currentNode; 079 080 private boolean traceEvaluations = defaultTraceEvaluations; 081 082 private Evaluation rootEvaluation; 083 084 private Evaluation currentEvaluation; 085 086 private Evaluation lastEvaluation; 087 088 private boolean keepLastEvaluation = defaultKeepLastEvaluation; 089 090 private Map<String, Object> values = new HashMap<String, Object>( 23 ); 091 092 private ClassResolver classResolver = DEFAULT_CLASS_RESOLVER; 093 094 private TypeConverter typeConverter = DEFAULT_TYPE_CONVERTER; 095 096 private MemberAccess memberAccess = DEFAULT_MEMBER_ACCESS; 097 098 static 099 { 100 String s; 101 102 RESERVED_KEYS.add( CONTEXT_CONTEXT_KEY ); 103 RESERVED_KEYS.add( ROOT_CONTEXT_KEY ); 104 RESERVED_KEYS.add( THIS_CONTEXT_KEY ); 105 RESERVED_KEYS.add( TRACE_EVALUATIONS_CONTEXT_KEY ); 106 RESERVED_KEYS.add( LAST_EVALUATION_CONTEXT_KEY ); 107 RESERVED_KEYS.add( KEEP_LAST_EVALUATION_CONTEXT_KEY ); 108 RESERVED_KEYS.add( CLASS_RESOLVER_CONTEXT_KEY ); 109 RESERVED_KEYS.add( TYPE_CONVERTER_CONTEXT_KEY ); 110 RESERVED_KEYS.add( MEMBER_ACCESS_CONTEXT_KEY ); 111 112 try 113 { 114 s = System.getProperty( PROPERTY_KEY_PREFIX + ".traceEvaluations" ); 115 if ( s != null ) 116 { 117 defaultTraceEvaluations = Boolean.valueOf( s.trim() ); 118 } 119 s = System.getProperty( PROPERTY_KEY_PREFIX + ".keepLastEvaluation" ); 120 if ( s != null ) 121 { 122 defaultKeepLastEvaluation = Boolean.valueOf( s.trim() ); 123 } 124 } 125 catch ( SecurityException ex ) 126 { 127 // restricted access environment, just keep defaults 128 } 129 } 130 131 private Stack<Class<?>> typeStack = new Stack<Class<?>>(); 132 133 private Stack<Class<?>> accessorStack = new Stack<Class<?>>(); 134 135 private int localReferenceCounter = 0; 136 137 private Map<String, LocalReference> localReferenceMap = null; 138 139 /** 140 * Constructs a new OgnlContext with the default class resolver, type converter and member access. 141 */ 142 public OgnlContext() 143 { 144 } 145 146 /** 147 * Constructs a new OgnlContext with the given class resolver, type converter and member access. If any of these 148 * parameters is null the default will be used. 149 */ 150 public OgnlContext( ClassResolver classResolver, TypeConverter typeConverter, MemberAccess memberAccess ) 151 { 152 this(); 153 if ( classResolver != null ) 154 { 155 this.classResolver = classResolver; 156 } 157 if ( typeConverter != null ) 158 { 159 this.typeConverter = typeConverter; 160 } 161 if ( memberAccess != null ) 162 { 163 this.memberAccess = memberAccess; 164 } 165 } 166 167 public OgnlContext( Map<String, Object> values ) 168 { 169 super(); 170 this.values = values; 171 } 172 173 public OgnlContext( ClassResolver classResolver, TypeConverter typeConverter, MemberAccess memberAccess, 174 Map<String, Object> values ) 175 { 176 this( classResolver, typeConverter, memberAccess ); 177 this.values = values; 178 } 179 180 public void setValues( Map<String, Object> value ) 181 { 182 values.putAll( value ); 183 } 184 185 public Map<String, Object> getValues() 186 { 187 return values; 188 } 189 190 public void setClassResolver( ClassResolver value ) 191 { 192 if ( value == null ) 193 { 194 throw new IllegalArgumentException( "cannot set ClassResolver to null" ); 195 } 196 classResolver = value; 197 } 198 199 public ClassResolver getClassResolver() 200 { 201 return classResolver; 202 } 203 204 public void setTypeConverter( TypeConverter value ) 205 { 206 if ( value == null ) 207 { 208 throw new IllegalArgumentException( "cannot set TypeConverter to null" ); 209 } 210 typeConverter = value; 211 } 212 213 public TypeConverter getTypeConverter() 214 { 215 return typeConverter; 216 } 217 218 public void setMemberAccess( MemberAccess value ) 219 { 220 if ( value == null ) 221 { 222 throw new IllegalArgumentException( "cannot set MemberAccess to null" ); 223 } 224 memberAccess = value; 225 } 226 227 public MemberAccess getMemberAccess() 228 { 229 return memberAccess; 230 } 231 232 public void setRoot( Object value ) 233 { 234 root = value; 235 accessorStack.clear(); 236 typeStack.clear(); 237 currentObject = value; 238 239 if ( currentObject != null ) 240 { 241 setCurrentType( currentObject.getClass() ); 242 } 243 } 244 245 public Object getRoot() 246 { 247 return root; 248 } 249 250 public boolean getTraceEvaluations() 251 { 252 return traceEvaluations; 253 } 254 255 public void setTraceEvaluations( boolean value ) 256 { 257 traceEvaluations = value; 258 } 259 260 public Evaluation getLastEvaluation() 261 { 262 return lastEvaluation; 263 } 264 265 public void setLastEvaluation( Evaluation value ) 266 { 267 lastEvaluation = value; 268 } 269 270 /** 271 * This method can be called when the last evaluation has been used and can be returned for reuse in the free pool 272 * maintained by the runtime. This is not a necessary step, but is useful for keeping memory usage down. This will 273 * recycle the last evaluation and then set the last evaluation to null. 274 */ 275 public void recycleLastEvaluation() 276 { 277 OgnlRuntime.getEvaluationPool().recycleAll( lastEvaluation ); 278 lastEvaluation = null; 279 } 280 281 /** 282 * Returns true if the last evaluation that was done on this context is retained and available through 283 * <code>getLastEvaluation()</code>. The default is true. 284 */ 285 public boolean getKeepLastEvaluation() 286 { 287 return keepLastEvaluation; 288 } 289 290 /** 291 * Sets whether the last evaluation that was done on this context is retained and available through 292 * <code>getLastEvaluation()</code>. The default is true. 293 */ 294 public void setKeepLastEvaluation( boolean value ) 295 { 296 keepLastEvaluation = value; 297 } 298 299 public void setCurrentObject( Object value ) 300 { 301 currentObject = value; 302 } 303 304 public Object getCurrentObject() 305 { 306 return currentObject; 307 } 308 309 public void setCurrentAccessor( Class<?> type ) 310 { 311 accessorStack.add( type ); 312 } 313 314 public Class<?> getCurrentAccessor() 315 { 316 if ( accessorStack.isEmpty() ) 317 { 318 return null; 319 } 320 321 return accessorStack.peek(); 322 } 323 324 public Class<?> getPreviousAccessor() 325 { 326 if ( accessorStack.isEmpty() ) 327 { 328 return null; 329 } 330 331 if ( accessorStack.size() > 1 ) 332 { 333 return accessorStack.get( accessorStack.size() - 2 ); 334 } 335 336 return null; 337 } 338 339 public Class<?> getFirstAccessor() 340 { 341 if ( accessorStack.isEmpty() ) 342 { 343 return null; 344 } 345 346 return accessorStack.get( 0 ); 347 } 348 349 /** 350 * Gets the current class type being evaluated on the stack, as set by {@link #setCurrentType(Class)}. 351 * 352 * @return The current object type, may be null. 353 */ 354 public Class<?> getCurrentType() 355 { 356 if ( typeStack.isEmpty() ) 357 { 358 return null; 359 } 360 361 return typeStack.peek(); 362 } 363 364 public void setCurrentType( Class<?> type ) 365 { 366 typeStack.add( type ); 367 } 368 369 /** 370 * Represents the last known object type on the evaluation stack, will be the value of the last known 371 * {@link #getCurrentType()}. 372 * 373 * @return The previous type of object on the stack, may be null. 374 */ 375 public Class<?> getPreviousType() 376 { 377 if ( typeStack.isEmpty() ) 378 { 379 return null; 380 } 381 382 if ( typeStack.size() > 1 ) 383 { 384 return typeStack.get( typeStack.size() - 2 ); 385 } 386 387 return null; 388 } 389 390 public void setPreviousType( Class<?> type ) 391 { 392 if ( typeStack.isEmpty() || typeStack.size() < 2 ) 393 { 394 return; 395 } 396 397 typeStack.set( typeStack.size() - 2, type ); 398 } 399 400 public Class<?> getFirstType() 401 { 402 if ( typeStack.isEmpty() ) 403 { 404 return null; 405 } 406 407 return typeStack.get( 0 ); 408 } 409 410 public void setCurrentNode( Node value ) 411 { 412 currentNode = value; 413 } 414 415 public Node getCurrentNode() 416 { 417 return currentNode; 418 } 419 420 /** 421 * Gets the current Evaluation from the top of the stack. This is the Evaluation that is in process of evaluating. 422 */ 423 public Evaluation getCurrentEvaluation() 424 { 425 return currentEvaluation; 426 } 427 428 public void setCurrentEvaluation( Evaluation value ) 429 { 430 currentEvaluation = value; 431 } 432 433 /** 434 * Gets the root of the evaluation stack. This Evaluation contains the node representing the root expression and the 435 * source is the root source object. 436 */ 437 public Evaluation getRootEvaluation() 438 { 439 return rootEvaluation; 440 } 441 442 public void setRootEvaluation( Evaluation value ) 443 { 444 rootEvaluation = value; 445 } 446 447 /** 448 * Returns the Evaluation at the relative index given. This should be zero or a negative number as a relative 449 * reference back up the evaluation stack. Therefore getEvaluation(0) returns the current Evaluation. 450 */ 451 public Evaluation getEvaluation( int relativeIndex ) 452 { 453 Evaluation result = null; 454 455 if ( relativeIndex <= 0 ) 456 { 457 result = currentEvaluation; 458 while ( ( ++relativeIndex < 0 ) && ( result != null ) ) 459 { 460 result = result.getParent(); 461 } 462 } 463 return result; 464 } 465 466 /** 467 * Pushes a new Evaluation onto the stack. This is done before a node evaluates. When evaluation is complete it 468 * should be popped from the stack via <code>popEvaluation()</code>. 469 */ 470 public void pushEvaluation( Evaluation value ) 471 { 472 if ( currentEvaluation != null ) 473 { 474 currentEvaluation.addChild( value ); 475 } 476 else 477 { 478 setRootEvaluation( value ); 479 } 480 setCurrentEvaluation( value ); 481 } 482 483 /** 484 * Pops the current Evaluation off of the top of the stack. This is done after a node has completed its evaluation. 485 */ 486 public Evaluation popEvaluation() 487 { 488 Evaluation result; 489 490 result = currentEvaluation; 491 setCurrentEvaluation( result.getParent() ); 492 if ( currentEvaluation == null ) 493 { 494 setLastEvaluation( getKeepLastEvaluation() ? result : null ); 495 setRootEvaluation( null ); 496 setCurrentNode( null ); 497 } 498 return result; 499 } 500 501 public int incrementLocalReferenceCounter() 502 { 503 return ++localReferenceCounter; 504 } 505 506 public void addLocalReference( String key, LocalReference reference ) 507 { 508 if ( localReferenceMap == null ) 509 { 510 localReferenceMap = new LinkedHashMap<String, LocalReference>(); 511 } 512 513 localReferenceMap.put( key, reference ); 514 } 515 516 public Map<String, LocalReference> getLocalReferences() 517 { 518 return localReferenceMap; 519 } 520 521 /* ================= Map interface ================= */ 522 public int size() 523 { 524 return values.size(); 525 } 526 527 public boolean isEmpty() 528 { 529 return values.isEmpty(); 530 } 531 532 public boolean containsKey( Object key ) 533 { 534 return values.containsKey( key ); 535 } 536 537 public boolean containsValue( Object value ) 538 { 539 return values.containsValue( value ); 540 } 541 542 public Object get( Object key ) 543 { 544 Object result = null; 545 546 // FIXME: complexity is O(n) 547 if ( RESERVED_KEYS.contains( key ) ) 548 { 549 if ( THIS_CONTEXT_KEY.equals( key ) ) 550 { 551 result = getCurrentObject(); 552 } 553 else if ( ROOT_CONTEXT_KEY.equals( key ) ) 554 { 555 result = getRoot(); 556 } 557 else if ( CONTEXT_CONTEXT_KEY.equals( key ) ) 558 { 559 result = this; 560 } 561 else if ( TRACE_EVALUATIONS_CONTEXT_KEY.equals( key ) ) 562 { 563 result = getTraceEvaluations() ? Boolean.TRUE : Boolean.FALSE; 564 } 565 else if ( LAST_EVALUATION_CONTEXT_KEY.equals( key ) ) 566 { 567 result = getLastEvaluation(); 568 } 569 else if ( KEEP_LAST_EVALUATION_CONTEXT_KEY.equals( key ) ) 570 { 571 result = getKeepLastEvaluation() ? Boolean.TRUE : Boolean.FALSE; 572 } 573 else if ( CLASS_RESOLVER_CONTEXT_KEY.equals( key ) ) 574 { 575 result = getClassResolver(); 576 } 577 else if ( TYPE_CONVERTER_CONTEXT_KEY.equals( key ) ) 578 { 579 result = getTypeConverter(); 580 } 581 else if ( MEMBER_ACCESS_CONTEXT_KEY.equals( key ) ) 582 { 583 result = getMemberAccess(); 584 } 585 } 586 else 587 { 588 result = values.get( key ); 589 } 590 return result; 591 } 592 593 public Object put( String key, Object value ) 594 { 595 Object result = null; 596 597 // FIXME: complexity is O(n) 598 if ( RESERVED_KEYS.contains( key ) ) 599 { 600 if ( CONTEXT_CONTEXT_KEY.equals( key ) ) 601 { 602 throw new IllegalArgumentException( "can't change " + CONTEXT_CONTEXT_KEY + " in context" ); 603 } 604 605 if ( THIS_CONTEXT_KEY.equals( key ) ) 606 { 607 result = getCurrentObject(); 608 setCurrentObject( value ); 609 } 610 else if ( ROOT_CONTEXT_KEY.equals( key ) ) 611 { 612 result = getRoot(); 613 setRoot( value ); 614 } 615 else if ( TRACE_EVALUATIONS_CONTEXT_KEY.equals( key ) ) 616 { 617 result = getTraceEvaluations() ? Boolean.TRUE : Boolean.FALSE; 618 setTraceEvaluations( OgnlOps.booleanValue( value ) ); 619 } 620 else if ( LAST_EVALUATION_CONTEXT_KEY.equals( key ) ) 621 { 622 result = getLastEvaluation(); 623 lastEvaluation = (Evaluation) value; 624 } 625 else if ( KEEP_LAST_EVALUATION_CONTEXT_KEY.equals( key ) ) 626 { 627 result = getKeepLastEvaluation() ? Boolean.TRUE : Boolean.FALSE; 628 setKeepLastEvaluation( OgnlOps.booleanValue( value ) ); 629 } 630 else if ( CLASS_RESOLVER_CONTEXT_KEY.equals( key ) ) 631 { 632 result = getClassResolver(); 633 setClassResolver( (ClassResolver) value ); 634 } 635 else if ( TYPE_CONVERTER_CONTEXT_KEY.equals( key ) ) 636 { 637 result = getTypeConverter(); 638 setTypeConverter( (TypeConverter) value ); 639 } 640 else if ( MEMBER_ACCESS_CONTEXT_KEY.equals( key ) ) 641 { 642 result = getMemberAccess(); 643 setMemberAccess( (MemberAccess) value ); 644 } 645 } 646 else 647 { 648 result = values.put( key, value ); 649 } 650 651 return result; 652 } 653 654 public Object remove( Object key ) 655 { 656 Object result = null; 657 658 // FIXME: complexity is O(n) 659 if ( RESERVED_KEYS.contains( key ) ) 660 { 661 if ( CONTEXT_CONTEXT_KEY.equals( key ) || TRACE_EVALUATIONS_CONTEXT_KEY.equals( key ) 662 || KEEP_LAST_EVALUATION_CONTEXT_KEY.equals( key ) ) 663 { 664 throw new IllegalArgumentException( "can't remove " + key + " from context" ); 665 } 666 667 if ( THIS_CONTEXT_KEY.equals( key ) ) 668 { 669 result = getCurrentObject(); 670 setCurrentObject( null ); 671 } 672 else if ( ROOT_CONTEXT_KEY.equals( key ) ) 673 { 674 result = getRoot(); 675 setRoot( null ); 676 } 677 else if ( LAST_EVALUATION_CONTEXT_KEY.equals( key ) ) 678 { 679 result = lastEvaluation; 680 setLastEvaluation( null ); 681 } 682 else if ( CLASS_RESOLVER_CONTEXT_KEY.equals( key ) ) 683 { 684 result = getClassResolver(); 685 setClassResolver( null ); 686 } 687 else if ( TYPE_CONVERTER_CONTEXT_KEY.equals( key ) ) 688 { 689 result = getTypeConverter(); 690 setTypeConverter( null ); 691 } 692 else if ( MEMBER_ACCESS_CONTEXT_KEY.equals( key ) ) 693 { 694 result = getMemberAccess(); 695 setMemberAccess( null ); 696 } 697 } 698 else 699 { 700 result = values.remove( key ); 701 } 702 return result; 703 } 704 705 public void putAll( Map<? extends String, ?> t ) 706 { 707 for ( Entry<? extends String, ?> entry : t.entrySet() ) 708 { 709 put( entry.getKey(), entry.getValue() ); 710 } 711 } 712 713 public void clear() 714 { 715 values.clear(); 716 typeStack.clear(); 717 accessorStack.clear(); 718 719 localReferenceCounter = 0; 720 if ( localReferenceMap != null ) 721 { 722 localReferenceMap.clear(); 723 } 724 725 setRoot( null ); 726 setCurrentObject( null ); 727 setRootEvaluation( null ); 728 setCurrentEvaluation( null ); 729 setLastEvaluation( null ); 730 setCurrentNode( null ); 731 setClassResolver( DEFAULT_CLASS_RESOLVER ); 732 setTypeConverter( DEFAULT_TYPE_CONVERTER ); 733 setMemberAccess( DEFAULT_MEMBER_ACCESS ); 734 } 735 736 public Set<String> keySet() 737 { 738 /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */ 739 return values.keySet(); 740 } 741 742 public Collection<Object> values() 743 { 744 /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */ 745 return values.values(); 746 } 747 748 public Set<Entry<String, Object>> entrySet() 749 { 750 /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */ 751 return values.entrySet(); 752 } 753 754 @Override 755 public boolean equals( Object o ) 756 { 757 return values.equals( o ); 758 } 759 760 @Override 761 public int hashCode() 762 { 763 return values.hashCode(); 764 } 765 }