View Javadoc

1   package org.apache.commons.ognl;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.ognl.enhance.LocalReference;
23  
24  import java.util.Collection;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.LinkedHashMap;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.Stack;
31  
32  /**
33   * This class defines the execution context for an OGNL expression
34   * 
35   * @author Luke Blanshard (blanshlu@netscape.net)
36   * @author Drew Davidson (drew@ognl.org)
37   */
38  public class OgnlContext
39      implements Map<String, Object>
40  {
41  
42      public static final String CONTEXT_CONTEXT_KEY = "context";
43  
44      public static final String ROOT_CONTEXT_KEY = "root";
45  
46      public static final String THIS_CONTEXT_KEY = "this";
47  
48      public static final String TRACE_EVALUATIONS_CONTEXT_KEY = "_traceEvaluations";
49  
50      public static final String LAST_EVALUATION_CONTEXT_KEY = "_lastEvaluation";
51  
52      public static final String KEEP_LAST_EVALUATION_CONTEXT_KEY = "_keepLastEvaluation";
53  
54      public static final String CLASS_RESOLVER_CONTEXT_KEY = "_classResolver";
55  
56      public static final String TYPE_CONVERTER_CONTEXT_KEY = "_typeConverter";
57  
58      public static final String MEMBER_ACCESS_CONTEXT_KEY = "_memberAccess";
59  
60      private static final String PROPERTY_KEY_PREFIX = "ognl";
61  
62      private static boolean defaultTraceEvaluations = false;
63  
64      private static boolean defaultKeepLastEvaluation = false;
65  
66      public static final DefaultClassResolver DEFAULT_CLASS_RESOLVER = new DefaultClassResolver();
67  
68      public static final TypeConverter DEFAULT_TYPE_CONVERTER = new DefaultTypeConverter();
69  
70      public static final MemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess( false );
71  
72      private static final Set<String> RESERVED_KEYS = new HashSet<String>( 11 );
73  
74      private Object root;
75  
76      private Object currentObject;
77  
78      private Node currentNode;
79  
80      private boolean traceEvaluations = defaultTraceEvaluations;
81  
82      private Evaluation rootEvaluation;
83  
84      private Evaluation currentEvaluation;
85  
86      private Evaluation lastEvaluation;
87  
88      private boolean keepLastEvaluation = defaultKeepLastEvaluation;
89  
90      private Map<String, Object> values = new HashMap<String, Object>( 23 );
91  
92      private ClassResolver classResolver = DEFAULT_CLASS_RESOLVER;
93  
94      private TypeConverter typeConverter = DEFAULT_TYPE_CONVERTER;
95  
96      private MemberAccess memberAccess = DEFAULT_MEMBER_ACCESS;
97  
98      static
99      {
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 }