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