001 /*
002 * The Apache Software License, Version 1.1
003 *
004 * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
005 *
006 * Redistribution and use in source and binary forms, with or without
007 * modification, are permitted provided that the following conditions
008 * are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright
011 * notice, this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright
014 * notice, this list of conditions and the following disclaimer in
015 * the documentation and/or other materials provided with the
016 * distribution.
017 *
018 * 3. The end-user documentation included with the redistribution, if
019 * any, must include the following acknowlegement:
020 * "This product includes software developed by the
021 * Caucho Technology (http://www.caucho.com/)."
022 * Alternately, this acknowlegement may appear in the software itself,
023 * if and wherever such third-party acknowlegements normally appear.
024 *
025 * 4. The names "Burlap", "Resin", and "Caucho" must not be used to
026 * endorse or promote products derived from this software without prior
027 * written permission. For written permission, please contact
028 * info@caucho.com.
029 *
030 * 5. Products derived from this software may not be called "Resin"
031 * nor may "Resin" appear in their names without prior written
032 * permission of Caucho Technology.
033 *
034 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037 * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
038 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
039 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
040 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
041 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
042 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
043 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
044 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
045 *
046 * @author Scott Ferguson
047 */
048
049 package com.caucho.burlap.client;
050
051 import com.caucho.burlap.io.AbstractBurlapInput;
052 import com.caucho.burlap.io.BurlapInput;
053 import com.caucho.burlap.io.BurlapOutput;
054 import com.caucho.burlap.io.BurlapRemoteObject;
055 import com.caucho.burlap.io.BurlapRemoteResolver;
056 import com.caucho.services.client.ServiceProxyFactory;
057
058 import javax.naming.Context;
059 import javax.naming.Name;
060 import javax.naming.NamingException;
061 import javax.naming.RefAddr;
062 import javax.naming.Reference;
063 import javax.naming.spi.ObjectFactory;
064 import java.io.IOException;
065 import java.io.InputStream;
066 import java.io.OutputStream;
067 import java.lang.reflect.Proxy;
068 import java.net.HttpURLConnection;
069 import java.net.MalformedURLException;
070 import java.net.URL;
071 import java.net.URLConnection;
072 import java.util.Hashtable;
073
074 /**
075 * Factory for creating Burlap client stubs. The returned stub will
076 * call the remote object for all methods.
077 *
078 * <pre>
079 * String url = "http://localhost:8080/ejb/hello";
080 * HelloHome hello = (HelloHome) factory.create(HelloHome.class, url);
081 * </pre>
082 *
083 * After creation, the stub can be like a regular Java class. Because
084 * it makes remote calls, it can throw more exceptions than a Java class.
085 * In particular, it may throw protocol exceptions.
086 *
087 * The factory can also be configured as a JNDI resource. The factory
088 * expects to parameters: "type" and "url", corresponding to the two
089 * arguments to <code>create</code>
090 *
091 * In Resin 3.0, the above example would be configured as:
092 * <pre>
093 * <reference>
094 * <name>hessian/hello</name>
095 * <factory>com.caucho.hessian.client.HessianProxyFactory</factory>
096 * <init url="http://localhost:8080/ejb/hello"/>
097 * type="test.HelloHome"/>
098 * </reference>
099 * </pre>
100 *
101 * To get the above resource, use JNDI as follows:
102 * <pre>
103 * Context ic = new InitialContext();
104 * HelloHome hello = (HelloHome) ic.lookup("java:comp/env/burlap/hello");
105 *
106 * System.out.println("Hello: " + hello.helloWorld());
107 * </pre>
108 *
109 * <h3>Authentication</h3>
110 *
111 * <p>The proxy can use HTTP basic authentication if the user and the
112 * password are set.
113 */
114 public class BurlapProxyFactory implements ServiceProxyFactory, ObjectFactory {
115 private BurlapRemoteResolver _resolver;
116
117 private String _user;
118 private String _password;
119 private String _basicAuth;
120
121 private long _readTimeout;
122 private boolean _isOverloadEnabled = false;
123
124 /**
125 * Creates the new proxy factory.
126 */
127 public BurlapProxyFactory()
128 {
129 _resolver = new BurlapProxyResolver(this);
130 }
131
132 /**
133 * Sets the user.
134 */
135 public void setUser(String user)
136 {
137 _user = user;
138 _basicAuth = null;
139 }
140
141 /**
142 * Sets the password.
143 */
144 public void setPassword(String password)
145 {
146 _password = password;
147 _basicAuth = null;
148 }
149
150 /**
151 * Returns true if overloaded methods are allowed (using mangling)
152 */
153 public boolean isOverloadEnabled()
154 {
155 return _isOverloadEnabled;
156 }
157
158 /**
159 * set true if overloaded methods are allowed (using mangling)
160 */
161 public void setOverloadEnabled(boolean isOverloadEnabled)
162 {
163 _isOverloadEnabled = isOverloadEnabled;
164 }
165
166 /**
167 * Returns the remote resolver.
168 */
169 public BurlapRemoteResolver getRemoteResolver()
170 {
171 return _resolver;
172 }
173
174 /**
175 * Creates the URL connection.
176 */
177 protected URLConnection openConnection(URL url)
178 throws IOException
179 {
180 URLConnection conn = url.openConnection();
181
182 conn.setDoOutput(true);
183
184 if (_basicAuth != null)
185 conn.setRequestProperty("Authorization", _basicAuth);
186 else if (_user != null && _password != null) {
187 _basicAuth = "Basic " + base64(_user + ":" + _password);
188 conn.setRequestProperty("Authorization", _basicAuth);
189 }
190
191 return conn;
192 }
193
194 /**
195 * Creates a new proxy with the specified URL. The API class uses
196 * the java.api.class value from _hessian_
197 *
198 * @param url the URL where the client object is located.
199 *
200 * @return a proxy to the object with the specified interface.
201 */
202 public Object create(String url)
203 throws MalformedURLException, ClassNotFoundException
204 {
205 BurlapMetaInfoAPI metaInfo;
206
207 metaInfo = (BurlapMetaInfoAPI) create(BurlapMetaInfoAPI.class, url);
208
209 String apiClassName =
210 (String) metaInfo._burlap_getAttribute("java.api.class");
211
212 if (apiClassName == null)
213 throw new BurlapRuntimeException(url + " has an unknown api.");
214
215 ClassLoader loader = Thread.currentThread().getContextClassLoader();
216
217 Class apiClass = Class.forName(apiClassName, false, loader);
218
219 return create(apiClass, url);
220 }
221
222 /**
223 * Creates a new proxy with the specified URL. The returned object
224 * is a proxy with the interface specified by api.
225 *
226 * <pre>
227 * String url = "http://localhost:8080/ejb/hello");
228 * HelloHome hello = (HelloHome) factory.create(HelloHome.class, url);
229 * </pre>
230 *
231 * @param api the interface the proxy class needs to implement
232 * @param url the URL where the client object is located.
233 *
234 * @return a proxy to the object with the specified interface.
235 */
236 public Object create(Class api, String urlName)
237 throws MalformedURLException
238 {
239 if (api == null)
240 throw new NullPointerException();
241
242 URL url = new URL(urlName);
243
244 try {
245 // clear old keepalive connections
246 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
247
248 conn.setConnectTimeout(10);
249 conn.setReadTimeout(10);
250
251 conn.setRequestProperty("Connection", "close");
252
253 InputStream is = conn.getInputStream();
254
255 is.close();
256
257 conn.disconnect();
258 } catch (IOException e) {
259 }
260
261 BurlapProxy handler = new BurlapProxy(this, url);
262
263 return Proxy.newProxyInstance(api.getClassLoader(),
264 new Class[] { api,
265 BurlapRemoteObject.class },
266 handler);
267 }
268
269 public AbstractBurlapInput getBurlapInput(InputStream is)
270 {
271 AbstractBurlapInput in = new BurlapInput(is);
272 in.setRemoteResolver(getRemoteResolver());
273
274 return in;
275 }
276
277 public BurlapOutput getBurlapOutput(OutputStream os)
278 {
279 BurlapOutput out = new BurlapOutput(os);
280
281 return out;
282 }
283
284 /**
285 * JNDI object factory so the proxy can be used as a resource.
286 */
287 public Object getObjectInstance(Object obj, Name name,
288 Context nameCtx,
289 Hashtable<?,?> environment)
290 throws Exception
291 {
292 Reference ref = (Reference) obj;
293
294 String api = null;
295 String url = null;
296 String user = null;
297 String password = null;
298
299 for (int i = 0; i < ref.size(); i++) {
300 RefAddr addr = ref.get(i);
301
302 String type = addr.getType();
303 String value = (String) addr.getContent();
304
305 if (type.equals("type"))
306 api = value;
307 else if (type.equals("url"))
308 url = value;
309 else if (type.equals("user"))
310 setUser(value);
311 else if (type.equals("password"))
312 setPassword(value);
313 }
314
315 if (url == null)
316 throw new NamingException("`url' must be configured for BurlapProxyFactory.");
317 // XXX: could use meta protocol to grab this
318 if (api == null)
319 throw new NamingException("`type' must be configured for BurlapProxyFactory.");
320
321 ClassLoader loader = Thread.currentThread().getContextClassLoader();
322 Class apiClass = Class.forName(api, false, loader);
323
324 return create(apiClass, url);
325 }
326
327 /**
328 * Creates the Base64 value.
329 */
330 private String base64(String value)
331 {
332 StringBuffer cb = new StringBuffer();
333
334 int i = 0;
335 for (i = 0; i + 2 < value.length(); i += 3) {
336 long chunk = (int) value.charAt(i);
337 chunk = (chunk << 8) + (int) value.charAt(i + 1);
338 chunk = (chunk << 8) + (int) value.charAt(i + 2);
339
340 cb.append(encode(chunk >> 18));
341 cb.append(encode(chunk >> 12));
342 cb.append(encode(chunk >> 6));
343 cb.append(encode(chunk));
344 }
345
346 if (i + 1 < value.length()) {
347 long chunk = (int) value.charAt(i);
348 chunk = (chunk << 8) + (int) value.charAt(i + 1);
349 chunk <<= 8;
350
351 cb.append(encode(chunk >> 18));
352 cb.append(encode(chunk >> 12));
353 cb.append(encode(chunk >> 6));
354 cb.append('=');
355 }
356 else if (i < value.length()) {
357 long chunk = (int) value.charAt(i);
358 chunk <<= 16;
359
360 cb.append(encode(chunk >> 18));
361 cb.append(encode(chunk >> 12));
362 cb.append('=');
363 cb.append('=');
364 }
365
366 return cb.toString();
367 }
368
369 public static char encode(long d)
370 {
371 d &= 0x3f;
372 if (d < 26)
373 return (char) (d + 'A');
374 else if (d < 52)
375 return (char) (d + 'a' - 26);
376 else if (d < 62)
377 return (char) (d + '0' - 52);
378 else if (d == 62)
379 return '+';
380 else
381 return '/';
382 }
383 }
384