1 /*
2 Copyright (c) 2007 Health Market Science, Inc.
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 package com.healthmarketscience.rmiio;
18
19 import java.lang.reflect.InvocationHandler;
20 import java.lang.reflect.Method;
21
22 import org.apache.commons.logging.Log;
23 import java.lang.reflect.Proxy;
24
25
26 /**
27 * Base class for implementing remote stub wrappers with builtin retry
28 * policies. Providers of remote interfaces can generate simple wrappers for
29 * use by a remote client which handle the retry functionality under the hood,
30 * simplifying client code dramatically. Note that this should only be done
31 * for <b>idempotent</b> method calls. See {@link RemoteInputStreamWrapper}
32 * and {@link RemoteOutputStreamWrapper} for example usage.
33 * <p>
34 * While subclassing presents an ability to fine tune the wrapper
35 * implementation, it may be difficult and/or unnecessary for many interfaces.
36 * In that case, a much simpler wrapper can be created using the {@link #wrap}
37 * method, which uses the java {@code Proxy} functionality to generate the
38 * wrapper implementation at run time. This may be slightly less efficient
39 * than a custom implementation since reflection is used for the actual method
40 * invocations, but for remote method calls that overhead is probably
41 * meaningless.
42 *
43 * @author James Ahlborn
44 */
45 public class RemoteWrapper<RemoteType>
46 implements InvocationHandler, RemoteClient
47 {
48
49 /** the handle to the remote interface which will do the real work of the
50 remote method calls */
51 protected final RemoteType _stub;
52 /** the retry policy we are using for our remote calls */
53 protected RemoteRetry _retry;
54 /** the log which will be used by the retry facility when making the remote
55 calls */
56 protected final Log _log;
57
58 public RemoteWrapper(RemoteType stub, RemoteRetry retry, Log log) {
59 if(stub == null) {
60 throw new IllegalArgumentException("Remote stub cannot be null");
61 }
62 _stub = stub;
63 _retry = retry;
64 _log = log;
65 }
66
67 /**
68 * Simple wrapper generator which creates a Proxy for the given remote
69 * interface. This proxy will make all the remote calls through the
70 * {@link #invoke} method which makes the actual method calls on the
71 * underlying stub within the retry logic.
72 *
73 * @param iface the remote interface to be implemented
74 * @param stub the underlying implementation of the remote interface which
75 * will actually make the remote calls
76 * @param retry the retry policy to use for the remote calls
77 * @param log the log to use during retry handling
78 *
79 * @return a proxy for the given interface using the given retry strategy
80 */
81 public static <R> R wrap(Class<R> iface, R stub,
82 RemoteRetry retry, Log log)
83 {
84 RemoteWrapper<R> wrapper = new RemoteWrapper<R>(stub, retry, log);
85 return iface.cast(Proxy.newProxyInstance(
86 Thread.currentThread().getContextClassLoader(),
87 new Class<?>[]{iface}, wrapper));
88 }
89
90 /**
91 * Gets the wrapper underlying a proxy created by a call to {@link #wrap}.
92 */
93 public static RemoteWrapper<?> getWrapper(Object proxy)
94 {
95 return (RemoteWrapper<?>)Proxy.getInvocationHandler(proxy);
96 }
97
98 public RemoteType getStub() {
99 return _stub;
100 }
101
102 public Log getLog() {
103 return _log;
104 }
105
106 public RemoteRetry getRemoteRetry() {
107 return _retry;
108 }
109
110 /**
111 * {@inheritDoc}
112 * <p>
113 * This may be useful for temporarily changing the retry policy for a
114 * specific set of calls (e.g. a startup/discovery sequence may be more
115 * forgiving than normal usage).
116 * <p>
117 * Note, this method is not thread-safe as this should only be used on a
118 * wrapper for which the caller has exclusive ownership (the retry policy
119 * will be changed for all users of the wrapper).
120 */
121 public void setRemoteRetry(RemoteRetry retry) {
122 _retry = ((retry != null) ? retry : DEFAULT_RETRY);
123 }
124
125 public Object invoke(Object proxy, final Method method, final Object[] args)
126 throws Throwable
127 {
128 // make the method call on the actual remote stub within the retry handler
129 return _retry.call(new RemoteRetry.Caller<Object>()
130 {
131 @Override
132 public Object call() throws Exception {
133 return method.invoke(_stub, method, args);
134 }
135 }, _log, Exception.class);
136 }
137
138 }