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
45              .getLogger(StateMachineProxyBuilder.class);
46  
47      private static final Object[] EMPTY_ARGUMENTS = new Object[0];
48  
49      private StateContextLookup contextLookup = new SingletonStateContextLookup();
50  
51      private EventFactory eventFactory = new DefaultEventFactory();
52  
53      private EventArgumentsInterceptor interceptor = null;
54  
55      private boolean ignoreUnhandledEvents = false;
56  
57      private boolean ignoreStateContextLookupFailure = false;
58      
59      private String name = null;
60  
61      /*
62       * The classloader to use. Iif null we will use the current thread's 
63       * context classloader.
64       */
65      private ClassLoader defaultCl = null; 
66  
67      public StateMachineProxyBuilder() {
68      }
69  
70      /**
71       * Sets the name of the proxy created by this builder. This will be used 
72       * by the proxies <code>toString()</code> method. If not specified a default
73       * auto generated name will be used.
74       * 
75       * @param name the name.
76       * @return this {@link StateMachineProxyBuilder} for method chaining.
77       */
78      public StateMachineProxyBuilder setName(String name) {
79          this.name = name;
80          return this;
81      }
82      
83      /**
84       * Sets the {@link StateContextLookup} to be used. The default is to use
85       * a {@link SingletonStateContextLookup}.
86       * 
87       * @param contextLookup the {@link StateContextLookup} to use.
88       * @return this {@link StateMachineProxyBuilder} for method chaining. 
89       */
90      public StateMachineProxyBuilder setStateContextLookup(
91              StateContextLookup contextLookup) {
92          this.contextLookup = contextLookup;
93          return this;
94      }
95  
96      /**
97       * Sets the {@link EventFactory} to be used. The default is to use a 
98       * {@link DefaultEventFactory}.
99       * 
100      * @param eventFactory the {@link EventFactory} to use.
101      * @return this {@link StateMachineProxyBuilder} for method chaining.
102      */
103     public StateMachineProxyBuilder setEventFactory(EventFactory eventFactory) {
104         this.eventFactory = eventFactory;
105         return this;
106     }
107 
108     /**
109      * Sets the {@link EventArgumentsInterceptor} to be used. By default no
110      * {@link EventArgumentsInterceptor} will be used.
111      * 
112      * @param interceptor the {@link EventArgumentsInterceptor} to use.
113      * @return this {@link StateMachineProxyBuilder} for method chaining.
114      */
115     public StateMachineProxyBuilder setEventArgumentsInterceptor(
116             EventArgumentsInterceptor interceptor) {
117         this.interceptor = interceptor;
118         return this;
119     }
120 
121     /**
122      * Sets whether events which have no handler in the current state will raise 
123      * an exception or be silently ignored. The default is to raise an 
124      * exception. 
125      * 
126      * @param b <code>true</code> to ignore context lookup failures.
127      * @return this {@link StateMachineProxyBuilder} for method chaining. 
128      */
129     public StateMachineProxyBuilder setIgnoreUnhandledEvents(boolean b) {
130         this.ignoreUnhandledEvents = b;
131         return this;
132     }
133 
134     /**
135      * Sets whether the failure to lookup a {@link StateContext} corresponding
136      * to a method call on the proxy produced by this builder will raise an
137      * exception or be silently ignored. The default is to raise an exception.
138      * 
139      * @param b <code>true</code> to ignore context lookup failures.
140      * @return this {@link StateMachineProxyBuilder} for method chaining. 
141      */
142     public StateMachineProxyBuilder setIgnoreStateContextLookupFailure(boolean b) {
143         this.ignoreStateContextLookupFailure = b;
144         return this;
145     }
146 
147     /**
148      * Sets the class loader to use for instantiating proxies. The default is
149      * to use the current threads context {@link ClassLoader} as returned by 
150      * {@link Thread#getContextClassLoader()}.
151      * 
152      * @param cl the class loader
153      * @return this {@link StateMachineProxyBuilder} for method chaining. 
154      */
155     public StateMachineProxyBuilder setClassLoader(ClassLoader cl) {
156         this.defaultCl = cl;
157         return this;
158     }
159 
160     /**
161      * Creates a proxy for the specified interface and which uses the specified 
162      * {@link StateMachine}.
163      * 
164      * @param iface the interface the proxy will implement.
165      * @param sm the {@link StateMachine} which will receive the events 
166      *        generated by the method calls on the proxy.
167      * @return the proxy object.
168      */
169     @SuppressWarnings("unchecked")
170     public <T> T create(Class<T> iface, StateMachine sm) {
171         return (T) create(new Class[] { iface }, sm);
172     }
173 
174     /**
175      * Creates a proxy for the specified interfaces and which uses the specified 
176      * {@link StateMachine}.
177      * 
178      * @param ifaces the interfaces the proxy will implement.
179      * @param sm the {@link StateMachine} which will receive the events 
180      *        generated by the method calls on the proxy.
181      * @return the proxy object.
182      */
183     public Object create(Class<?>[] ifaces, StateMachine sm) {
184         ClassLoader cl = defaultCl;
185         if (cl == null) {
186            cl = Thread.currentThread().getContextClassLoader();
187         }
188 
189         InvocationHandler handler = new MethodInvocationHandler(sm,
190                 contextLookup, interceptor, eventFactory,
191                 ignoreUnhandledEvents, ignoreStateContextLookupFailure, name);
192 
193         return Proxy.newProxyInstance(cl, ifaces, handler);
194     }
195     
196     private static class MethodInvocationHandler implements InvocationHandler {
197         private final StateMachine sm;
198         private final StateContextLookup contextLookup;
199         private final EventArgumentsInterceptor interceptor;
200         private final EventFactory eventFactory;
201         private final boolean ignoreUnhandledEvents;
202         private final boolean ignoreStateContextLookupFailure;
203         private final String name;
204         
205         public MethodInvocationHandler(StateMachine sm, StateContextLookup contextLookup,
206                 EventArgumentsInterceptor interceptor, EventFactory eventFactory,
207                 boolean ignoreUnhandledEvents, boolean ignoreStateContextLookupFailure, 
208                 String name) {
209             
210             this.contextLookup = contextLookup;
211             this.sm = sm;
212             this.interceptor = interceptor;
213             this.eventFactory = eventFactory;
214             this.ignoreUnhandledEvents = ignoreUnhandledEvents;
215             this.ignoreStateContextLookupFailure = ignoreStateContextLookupFailure;
216             this.name = name;
217         }
218         
219         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
220             if ("hashCode".equals(method.getName()) && args == null) {
221                 return new Integer(System.identityHashCode(proxy));
222             }
223             if ("equals".equals(method.getName()) && args.length == 1) {
224                 return Boolean.valueOf(proxy == args[0]);
225             }
226             if ("toString".equals(method.getName()) && args == null) {
227                 return (name != null ? name : proxy.getClass().getName()) + "@" 
228                         + Integer.toHexString(System.identityHashCode(proxy));
229             }
230 
231             if (log.isDebugEnabled()) {
232                 log.debug("Method invoked: " + method);
233             }
234 
235             args = args == null ? EMPTY_ARGUMENTS : args;
236             if (interceptor != null) {
237                 args = interceptor.modify(args);
238             }
239 
240             StateContext context = contextLookup.lookup(args);
241 
242             if (context == null) {
243                 if (ignoreStateContextLookupFailure) {
244                     return null;
245                 }
246                 throw new IllegalStateException("Cannot determine state "
247                         + "context for method invocation: " + method);
248             }
249 
250             Event event = eventFactory.create(context, method, args);
251 
252             try {
253                 sm.handle(event);
254             } catch (UnhandledEventException uee) {
255                 if (!ignoreUnhandledEvents) {
256                     throw uee;
257                 }
258             }
259 
260             return null;
261         }
262     }
263 }