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 <code>true</code> 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 <code>true</code> 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 iface the interface the proxy will implement.
    162      * @param sm the {@link StateMachine} which will receive the events
    163      *        generated by the method calls on the proxy.
    164      * @return the proxy object.
    165      */
    166     @SuppressWarnings("unchecked")
    167     public <T> T create(Class<T> iface, StateMachine sm) {
    168         return (T) create(new Class[] { iface }, sm);
    169     }
    170
    171     /**
    172      * Creates a proxy for the specified interfaces and which uses the specified
    173      * {@link StateMachine}.
    174      *
    175      * @param ifaces the interfaces the proxy will implement.
    176      * @param sm the {@link StateMachine} which will receive the events
    177      *        generated by the method calls on the proxy.
    178      * @return the proxy object.
    179      */
    180     public Object create(Class<?>[] ifaces, StateMachine sm) {
    181         ClassLoader cl = defaultCl;
    182         if (cl == null) {
    183             cl = Thread.currentThread().getContextClassLoader();
    184         }
    185
    186         InvocationHandler handler = new MethodInvocationHandler(sm, contextLookup, interceptor, eventFactory,
    187                 ignoreUnhandledEvents, ignoreStateContextLookupFailure, name);
    188
    189         return Proxy.newProxyInstance(cl, ifaces, handler);
    190     }
    191
    192     private static class MethodInvocationHandler implements InvocationHandler {
    193         private final StateMachine sm;
    194
    195         private final StateContextLookup contextLookup;
    196
    197         private final EventArgumentsInterceptor interceptor;
    198
    199         private final EventFactory eventFactory;
    200
    201         private final boolean ignoreUnhandledEvents;
    202
    203         private final boolean ignoreStateContextLookupFailure;
    204
    205         private final String name;
    206
    207         public MethodInvocationHandler(StateMachine sm, StateContextLookup contextLookup,
    208                 EventArgumentsInterceptor interceptor, EventFactory eventFactory, boolean ignoreUnhandledEvents,
    209                 boolean ignoreStateContextLookupFailure, 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 " + "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 }