View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.javaflow.bytecode;
18  
19  import org.apache.commons.javaflow.utils.ReflectionUtils;
20  import org.apache.commons.javaflow.ContinuationDeath;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  
24  /**
25   * Adds additional behaviors necessary for stack capture/restore
26   * on top of {@link Stack}.
27   *
28   * @author Kohsuke Kawaguchi
29   */
30  public final class StackRecorder extends Stack {
31  
32      private static final Log log = LogFactory.getLog(StackRecorder.class);
33      private static final long serialVersionUID = 2L;
34  
35      private static final ThreadLocal threadMap = new ThreadLocal();
36  
37      /**
38       * True, if the continuation restores the previous stack trace to the last
39       * invocation of suspend().
40       *
41       * <p>
42       * This field is accessed from the byte code injected into application code,
43       * and therefore defining a wrapper get method makes it awkward to
44       * step through the user code. That's why this field is public.
45       */
46      public transient boolean isRestoring = false;
47  
48      /**
49       * True, is the continuation freeze the strack trace, and stops the
50       * continuation.
51       *
52       * @see #isRestoring
53       */
54      public transient boolean isCapturing = false;
55  
56      /** Context object given by the user */
57      private transient Object context;
58  
59      /**
60       * Creates a new empty {@link StackRecorder} that runs the given target.
61       */
62      public StackRecorder( final Runnable pTarget ) {
63          super(pTarget);
64      }
65  
66      /**
67       * Creates a clone of the given {@link StackRecorder}.
68       */
69      public StackRecorder(final Stack pParent) {
70          super(pParent);
71      }
72  
73      public static void suspend() {
74          log.debug("suspend()");
75  
76          final StackRecorder stackRecorder = get();
77          if(stackRecorder == null) {
78              throw new IllegalStateException("No continuation is running");
79          }
80  
81          stackRecorder.isCapturing = !stackRecorder.isRestoring;
82          stackRecorder.isRestoring = false;
83      }
84  
85      public StackRecorder execute(final Object context) {
86          final StackRecorder old = registerThread();
87          try {
88              isRestoring = !isEmpty(); // start restoring if we have a filled stack
89              this.context = context;
90              
91              if (isRestoring) {
92                  log.debug("Restoring state of " + ReflectionUtils.getClassName(runnable) + "/" + ReflectionUtils.getClassLoaderName(runnable));
93              }
94              
95              log.debug("calling runnable");
96              runnable.run();
97  
98              if (isCapturing) {
99                  if(isEmpty()) {
100                     // if we were really capturing the stack, at least we should have
101                     // one object in the reference stack. Otherwise, it usually means
102                     // that the application wasn't instrumented correctly.
103                     throw new IllegalStateException("stack corruption. Is "+runnable.getClass()+" instrumented for javaflow?");
104                 }
105                 // top of the reference stack is the object that we'll call into
106                 // when resuming this continuation. we have a separate Runnable
107                 // for this, so throw it away
108                 popReference();
109                 return this;
110             } else {
111                 return null;    // nothing more to continue
112             }
113         } catch(ContinuationDeath cd) {
114             // this isn't an error, so no need to log
115             throw cd;
116         } catch(Error e) {
117             log.error(e.getMessage(),e);
118             throw e;
119         } catch(RuntimeException e) {
120             log.error(e.getMessage(),e);
121             throw e;
122         } finally {
123             this.context = null;
124             deregisterThread(old);
125         }
126     }
127 
128     public Object getContext() {
129         return context;
130     }
131 
132     /**
133      * Bind this stack recorder to running thread.
134      */
135     private StackRecorder registerThread() {
136         StackRecorder old = get();
137         threadMap.set(this);
138         return old;
139     }
140 
141     /**
142      * Unbind the current stack recorder to running thread.
143      */
144     private void deregisterThread(final StackRecorder old) {
145         threadMap.set(old);
146     }
147 
148     /**
149      * Return the continuation, which is associated to the current thread.
150      */
151     public static StackRecorder get() {
152         return (StackRecorder)threadMap.get();
153     }
154 }