View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.statemachine;
21  
22  import java.lang.reflect.InvocationHandler;
23  import java.lang.reflect.Method;
24  import java.lang.reflect.Proxy;
25  
26  import org.apache.mina.statemachine.context.SingletonStateContextLookup;
27  import org.apache.mina.statemachine.context.StateContext;
28  import org.apache.mina.statemachine.context.StateContextLookup;
29  import org.apache.mina.statemachine.event.DefaultEventFactory;
30  import org.apache.mina.statemachine.event.Event;
31  import org.apache.mina.statemachine.event.EventArgumentsInterceptor;
32  import org.apache.mina.statemachine.event.EventFactory;
33  import org.apache.mina.statemachine.event.UnhandledEventException;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * Used to create proxies which will forward all method calls on them to a
39   * {@link StateMachine}.
40   *
41   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
42   */
43  public class StateMachineProxyBuilder {
44      private static final Logger log = LoggerFactory.getLogger(StateMachineProxyBuilder.class);
45  
46      private static final Object[] EMPTY_ARGUMENTS = new Object[0];
47  
48      private StateContextLookup contextLookup = new SingletonStateContextLookup();
49  
50      private EventFactory eventFactory = new DefaultEventFactory();
51  
52      private EventArgumentsInterceptor interceptor = null;
53  
54      private boolean ignoreUnhandledEvents = false;
55  
56      private boolean ignoreStateContextLookupFailure = false;
57  
58      private String name = null;
59  
60      /*
61       * The classloader to use. Iif null we will use the current thread's 
62       * context classloader.
63       */
64      private ClassLoader defaultCl = null;
65  
66      public StateMachineProxyBuilder() {
67      }
68  
69      /**
70       * Sets the name of the proxy created by this builder. This will be used 
71       * by the proxies <code>toString()</code> method. If not specified a default
72       * auto generated name will be used.
73       * 
74       * @param name the name.
75       * @return this {@link StateMachineProxyBuilder} for method chaining.
76       */
77      public StateMachineProxyBuilder setName(String name) {
78          this.name = name;
79          return this;
80      }
81  
82      /**
83       * Sets the {@link StateContextLookup} to be used. The default is to use
84       * a {@link SingletonStateContextLookup}.
85       * 
86       * @param contextLookup the {@link StateContextLookup} to use.
87       * @return this {@link StateMachineProxyBuilder} for method chaining. 
88       */
89      public StateMachineProxyBuilder setStateContextLookup(StateContextLookup contextLookup) {
90          this.contextLookup = contextLookup;
91          return this;
92      }
93  
94      /**
95       * Sets the {@link EventFactory} to be used. The default is to use a 
96       * {@link DefaultEventFactory}.
97       * 
98       * @param eventFactory the {@link EventFactory} to use.
99       * @return this {@link StateMachineProxyBuilder} for method chaining.
100      */
101     public StateMachineProxyBuilder setEventFactory(EventFactory eventFactory) {
102         this.eventFactory = eventFactory;
103         return this;
104     }
105 
106     /**
107      * Sets the {@link EventArgumentsInterceptor} to be used. By default no
108      * {@link EventArgumentsInterceptor} will be used.
109      * 
110      * @param interceptor the {@link EventArgumentsInterceptor} to use.
111      * @return this {@link StateMachineProxyBuilder} for method chaining.
112      */
113     public StateMachineProxyBuilder setEventArgumentsInterceptor(EventArgumentsInterceptor interceptor) {
114         this.interceptor = interceptor;
115         return this;
116     }
117 
118     /**
119      * Sets whether events which have no handler in the current state will raise 
120      * an exception or be silently ignored. The default is to raise an 
121      * exception. 
122      * 
123      * @param b <tt>true</tt> to ignore context lookup failures.
124      * @return this {@link StateMachineProxyBuilder} for method chaining. 
125      */
126     public StateMachineProxyBuilder setIgnoreUnhandledEvents(boolean b) {
127         this.ignoreUnhandledEvents = b;
128         return this;
129     }
130 
131     /**
132      * Sets whether the failure to lookup a {@link StateContext} corresponding
133      * to a method call on the proxy produced by this builder will raise an
134      * exception or be silently ignored. The default is to raise an exception.
135      * 
136      * @param b <tt>true</tt> to ignore context lookup failures.
137      * @return this {@link StateMachineProxyBuilder} for method chaining. 
138      */
139     public StateMachineProxyBuilder setIgnoreStateContextLookupFailure(boolean b) {
140         this.ignoreStateContextLookupFailure = b;
141         return this;
142     }
143 
144     /**
145      * Sets the class loader to use for instantiating proxies. The default is
146      * to use the current threads context {@link ClassLoader} as returned by 
147      * {@link Thread#getContextClassLoader()}.
148      * 
149      * @param cl the class loader
150      * @return this {@link StateMachineProxyBuilder} for method chaining. 
151      */
152     public StateMachineProxyBuilder setClassLoader(ClassLoader cl) {
153         this.defaultCl = cl;
154         return this;
155     }
156 
157     /**
158      * Creates a proxy for the specified interface and which uses the specified 
159      * {@link StateMachine}.
160      * 
161      * @param <T> The specified interface type
162      * @param iface the interface the proxy will implement.
163      * @param sm the {@link StateMachine} which will receive the events 
164      *        generated by the method calls on the proxy.
165      * @return the proxy object.
166      */
167     @SuppressWarnings("unchecked")
168     public <T> T create(Class<T> iface, StateMachine sm) {
169         return (T) create(new Class[] { iface }, sm);
170     }
171 
172     /**
173      * Creates a proxy for the specified interfaces and which uses the specified 
174      * {@link StateMachine}.
175      * 
176      * @param ifaces the interfaces the proxy will implement.
177      * @param sm the {@link StateMachine} which will receive the events 
178      *        generated by the method calls on the proxy.
179      * @return the proxy object.
180      */
181     public Object create(Class<?>[] ifaces, StateMachine sm) {
182         ClassLoader cl = defaultCl;
183         if (cl == null) {
184             cl = Thread.currentThread().getContextClassLoader();
185         }
186 
187         InvocationHandler handler = new MethodInvocationHandler(sm, contextLookup, interceptor, eventFactory,
188                 ignoreUnhandledEvents, ignoreStateContextLookupFailure, name);
189 
190         return Proxy.newProxyInstance(cl, ifaces, handler);
191     }
192 
193     private static class MethodInvocationHandler implements InvocationHandler {
194         private final StateMachine sm;
195 
196         private final StateContextLookup contextLookup;
197 
198         private final EventArgumentsInterceptor interceptor;
199 
200         private final EventFactory eventFactory;
201 
202         private final boolean ignoreUnhandledEvents;
203 
204         private final boolean ignoreStateContextLookupFailure;
205 
206         private final String name;
207 
208         public MethodInvocationHandler(StateMachine sm, StateContextLookup contextLookup,
209                 EventArgumentsInterceptor interceptor, EventFactory eventFactory, boolean ignoreUnhandledEvents,
210                 boolean ignoreStateContextLookupFailure, String name) {
211 
212             this.contextLookup = contextLookup;
213             this.sm = sm;
214             this.interceptor = interceptor;
215             this.eventFactory = eventFactory;
216             this.ignoreUnhandledEvents = ignoreUnhandledEvents;
217             this.ignoreStateContextLookupFailure = ignoreStateContextLookupFailure;
218             this.name = name;
219         }
220 
221         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
222             if ("hashCode".equals(method.getName()) && args == null) {
223                 return Integer.valueOf(System.identityHashCode(proxy));
224             }
225             if ("equals".equals(method.getName()) && args.length == 1) {
226                 return Boolean.valueOf(proxy == args[0]);
227             }
228             if ("toString".equals(method.getName()) && args == null) {
229                 return (name != null ? name : proxy.getClass().getName()) + "@"
230                         + Integer.toHexString(System.identityHashCode(proxy));
231             }
232 
233             if (log.isDebugEnabled()) {
234                 log.debug("Method invoked: " + method);
235             }
236 
237             args = args == null ? EMPTY_ARGUMENTS : args;
238             if (interceptor != null) {
239                 args = interceptor.modify(args);
240             }
241 
242             StateContext context = contextLookup.lookup(args);
243 
244             if (context == null) {
245                 if (ignoreStateContextLookupFailure) {
246                     return null;
247                 }
248                 throw new IllegalStateException("Cannot determine state " + "context for method invocation: " + method);
249             }
250 
251             Event event = eventFactory.create(context, method, args);
252 
253             try {
254                 sm.handle(event);
255             } catch (UnhandledEventException uee) {
256                 if (!ignoreUnhandledEvents) {
257                     throw uee;
258                 }
259             }
260 
261             return null;
262         }
263     }
264 }