001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.mina.statemachine; 021 022import java.lang.reflect.InvocationHandler; 023import java.lang.reflect.Method; 024import java.lang.reflect.Proxy; 025 026import org.apache.mina.statemachine.context.SingletonStateContextLookup; 027import org.apache.mina.statemachine.context.StateContext; 028import org.apache.mina.statemachine.context.StateContextLookup; 029import org.apache.mina.statemachine.event.DefaultEventFactory; 030import org.apache.mina.statemachine.event.Event; 031import org.apache.mina.statemachine.event.EventArgumentsInterceptor; 032import org.apache.mina.statemachine.event.EventFactory; 033import org.apache.mina.statemachine.event.UnhandledEventException; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037/** 038 * Used to create proxies which will forward all method calls on them to a 039 * {@link StateMachine}. 040 * 041 * @author <a href="http://mina.apache.org">Apache MINA Project</a> 042 */ 043public class StateMachineProxyBuilder { 044 private static final Logger log = LoggerFactory.getLogger(StateMachineProxyBuilder.class); 045 046 private static final Object[] EMPTY_ARGUMENTS = new Object[0]; 047 048 private StateContextLookup contextLookup = new SingletonStateContextLookup(); 049 050 private EventFactory eventFactory = new DefaultEventFactory(); 051 052 private EventArgumentsInterceptor interceptor = null; 053 054 private boolean ignoreUnhandledEvents = false; 055 056 private boolean ignoreStateContextLookupFailure = false; 057 058 private String name = null; 059 060 /* 061 * The classloader to use. Iif null we will use the current thread's 062 * context classloader. 063 */ 064 private ClassLoader defaultCl = null; 065 066 public StateMachineProxyBuilder() { 067 } 068 069 /** 070 * Sets the name of the proxy created by this builder. This will be used 071 * by the proxies <code>toString()</code> method. If not specified a default 072 * auto generated name will be used. 073 * 074 * @param name the name. 075 * @return this {@link StateMachineProxyBuilder} for method chaining. 076 */ 077 public StateMachineProxyBuilder setName(String name) { 078 this.name = name; 079 return this; 080 } 081 082 /** 083 * Sets the {@link StateContextLookup} to be used. The default is to use 084 * a {@link SingletonStateContextLookup}. 085 * 086 * @param contextLookup the {@link StateContextLookup} to use. 087 * @return this {@link StateMachineProxyBuilder} for method chaining. 088 */ 089 public StateMachineProxyBuilder setStateContextLookup(StateContextLookup contextLookup) { 090 this.contextLookup = contextLookup; 091 return this; 092 } 093 094 /** 095 * Sets the {@link EventFactory} to be used. The default is to use a 096 * {@link DefaultEventFactory}. 097 * 098 * @param eventFactory the {@link EventFactory} to use. 099 * @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}