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 "Hessian", "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.hessian.client;
050:
051: import com.caucho.hessian.io.*;
052: import com.caucho.services.client.ServiceProxyFactory;
053:
054: import javax.naming.Context;
055: import javax.naming.Name;
056: import javax.naming.NamingException;
057: import javax.naming.RefAddr;
058: import javax.naming.Reference;
059: import javax.naming.spi.ObjectFactory;
060: import java.io.IOException;
061: import java.io.InputStream;
062: import java.io.OutputStream;
063: import java.io.PrintWriter;
064: import java.lang.reflect.InvocationHandler;
065: import java.lang.reflect.Proxy;
066: import java.net.MalformedURLException;
067: import java.net.URL;
068: import java.net.URLConnection;
069: import java.util.Hashtable;
070: import java.util.logging.Logger;
071:
072: /**
073: * Factory for creating Hessian client stubs. The returned stub will
074: * call the remote object for all methods.
075: *
076: * <pre>
077: * String url = "http://localhost:8080/ejb/hello";
078: * HelloHome hello = (HelloHome) factory.create(HelloHome.class, url);
079: * </pre>
080: *
081: * After creation, the stub can be like a regular Java class. Because
082: * it makes remote calls, it can throw more exceptions than a Java class.
083: * In particular, it may throw protocol exceptions.
084: *
085: * The factory can also be configured as a JNDI resource. The factory
086: * expects to parameters: "type" and "url", corresponding to the two
087: * arguments to <code>create</code>
088: *
089: * In Resin 3.0, the above example would be configured as:
090: * <pre>
091: * <reference>
092: * <jndi-name>hessian/hello</jndi-name>
093: * <factory>com.caucho.hessian.client.HessianProxyFactory</factory>
094: * <init-param url="http://localhost:8080/ejb/hello"/>
095: * <init-param type="test.HelloHome"/>
096: * </reference>
097: * </pre>
098: *
099: * To get the above resource, use JNDI as follows:
100: * <pre>
101: * Context ic = new InitialContext();
102: * HelloHome hello = (HelloHome) ic.lookup("java:comp/env/hessian/hello");
103: *
104: * System.out.println("Hello: " + hello.helloWorld());
105: * </pre>
106: *
107: * <h3>Authentication</h3>
108: *
109: * <p>The proxy can use HTTP basic authentication if the user and the
110: * password are set.
111: */
112: public class HessianProxyFactory implements ServiceProxyFactory,
113: ObjectFactory {
114: protected static Logger log = Logger
115: .getLogger(HessianProxyFactory.class.getName());
116:
117: private SerializerFactory _serializerFactory;
118: private HessianRemoteResolver _resolver;
119:
120: private String _user;
121: private String _password;
122: private String _basicAuth;
123:
124: private boolean _isOverloadEnabled = false;
125:
126: private boolean _isHessian2Reply = true;
127: private boolean _isHessian2Request = false;
128:
129: private boolean _isChunkedPost = true;
130: private boolean _isDebug = false;
131:
132: private long _readTimeout = -1;
133:
134: private String _connectionFactoryName = "jms/ConnectionFactory";
135:
136: /**
137: * Creates the new proxy factory.
138: */
139: public HessianProxyFactory() {
140: _resolver = new HessianProxyResolver(this );
141: }
142:
143: /**
144: * Sets the user.
145: */
146: public void setUser(String user) {
147: _user = user;
148: _basicAuth = null;
149: }
150:
151: /**
152: * Sets the password.
153: */
154: public void setPassword(String password) {
155: _password = password;
156: _basicAuth = null;
157: }
158:
159: /**
160: * Sets the name of the connection factory to use when connecting
161: * to JMS Hessian services.
162: */
163: public void setConnectionFactoryName(String connectionFactoryName) {
164: _connectionFactoryName = connectionFactoryName;
165: }
166:
167: /**
168: * Sets the debug
169: */
170: public void setDebug(boolean isDebug) {
171: _isDebug = isDebug;
172: }
173:
174: /**
175: * Gets the debug
176: */
177: public boolean isDebug() {
178: return _isDebug;
179: }
180:
181: /**
182: * Returns true if overloaded methods are allowed (using mangling)
183: */
184: public boolean isOverloadEnabled() {
185: return _isOverloadEnabled;
186: }
187:
188: /**
189: * set true if overloaded methods are allowed (using mangling)
190: */
191: public void setOverloadEnabled(boolean isOverloadEnabled) {
192: _isOverloadEnabled = isOverloadEnabled;
193: }
194:
195: /**
196: * Set true if should use chunked encoding on the request.
197: */
198: public void setChunkedPost(boolean isChunked) {
199: _isChunkedPost = isChunked;
200: }
201:
202: /**
203: * Set true if should use chunked encoding on the request.
204: */
205: public boolean isChunkedPost() {
206: return _isChunkedPost;
207: }
208:
209: /**
210: * The socket timeout on requests in milliseconds.
211: */
212: public long getReadTimeout() {
213: return _readTimeout;
214: }
215:
216: /**
217: * The socket timeout on requests in milliseconds.
218: */
219: public void setReadTimeout(long timeout) {
220: _readTimeout = timeout;
221: }
222:
223: /**
224: * True if the proxy can read Hessian 2 responses.
225: */
226: public void setHessian2Reply(boolean isHessian2) {
227: _isHessian2Reply = isHessian2;
228: }
229:
230: /**
231: * True if the proxy should send Hessian 2 requests.
232: */
233: public void setHessian2Request(boolean isHessian2) {
234: _isHessian2Request = isHessian2;
235:
236: if (isHessian2)
237: _isHessian2Reply = true;
238: }
239:
240: /**
241: * Returns the remote resolver.
242: */
243: public HessianRemoteResolver getRemoteResolver() {
244: return _resolver;
245: }
246:
247: /**
248: * Sets the serializer factory.
249: */
250: public void setSerializerFactory(SerializerFactory factory) {
251: _serializerFactory = factory;
252: }
253:
254: /**
255: * Gets the serializer factory.
256: */
257: public SerializerFactory getSerializerFactory() {
258: if (_serializerFactory == null)
259: _serializerFactory = new SerializerFactory();
260:
261: return _serializerFactory;
262: }
263:
264: /**
265: * Creates the URL connection.
266: */
267: protected URLConnection openConnection(URL url) throws IOException {
268: URLConnection conn = url.openConnection();
269:
270: conn.setDoOutput(true);
271:
272: if (_readTimeout > 0) {
273: try {
274: conn.setReadTimeout((int) _readTimeout);
275: } catch (Throwable e) {
276: }
277: }
278:
279: conn
280: .setRequestProperty("Content-Type",
281: "x-application/hessian");
282:
283: if (_basicAuth != null)
284: conn.setRequestProperty("Authorization", _basicAuth);
285: else if (_user != null && _password != null) {
286: _basicAuth = "Basic " + base64(_user + ":" + _password);
287: conn.setRequestProperty("Authorization", _basicAuth);
288: }
289:
290: return conn;
291: }
292:
293: /**
294: * Creates a new proxy with the specified URL. The API class uses
295: * the java.api.class value from _hessian_
296: *
297: * @param url the URL where the client object is located.
298: *
299: * @return a proxy to the object with the specified interface.
300: */
301: public Object create(String url) throws MalformedURLException,
302: ClassNotFoundException {
303: HessianMetaInfoAPI metaInfo;
304:
305: metaInfo = (HessianMetaInfoAPI) create(
306: HessianMetaInfoAPI.class, url);
307:
308: String apiClassName = (String) metaInfo
309: ._hessian_getAttribute("java.api.class");
310:
311: if (apiClassName == null)
312: throw new HessianRuntimeException(url
313: + " has an unknown api.");
314:
315: ClassLoader loader = Thread.currentThread()
316: .getContextClassLoader();
317:
318: Class apiClass = Class.forName(apiClassName, false, loader);
319:
320: return create(apiClass, url);
321: }
322:
323: /**
324: * Creates a new proxy with the specified URL. The returned object
325: * is a proxy with the interface specified by api.
326: *
327: * <pre>
328: * String url = "http://localhost:8080/ejb/hello");
329: * HelloHome hello = (HelloHome) factory.create(HelloHome.class, url);
330: * </pre>
331: *
332: * @param api the interface the proxy class needs to implement
333: * @param url the URL where the client object is located.
334: *
335: * @return a proxy to the object with the specified interface.
336: */
337: public Object create(Class api, String urlName)
338: throws MalformedURLException {
339: return create(api, urlName, Thread.currentThread()
340: .getContextClassLoader());
341: }
342:
343: /**
344: * Creates a new proxy with the specified URL. The returned object
345: * is a proxy with the interface specified by api.
346: *
347: * <pre>
348: * String url = "http://localhost:8080/ejb/hello");
349: * HelloHome hello = (HelloHome) factory.create(HelloHome.class, url);
350: * </pre>
351: *
352: * @param api the interface the proxy class needs to implement
353: * @param url the URL where the client object is located.
354: *
355: * @return a proxy to the object with the specified interface.
356: */
357: public Object create(Class api, String urlName, ClassLoader loader)
358: throws MalformedURLException {
359: if (api == null)
360: throw new NullPointerException(
361: "api must not be null for HessianProxyFactory.create()");
362: InvocationHandler handler = null;
363:
364: if (false && urlName.startsWith("jms:")) {
365: /*
366: String jndiName = urlName.substring("jms:".length());
367:
368: try {
369: handler = new HessianJMSProxy(this, jndiName, _connectionFactoryName);
370: } catch (Exception e) {
371: log.info("Unable to create JMS proxy: " + e);
372: return null;
373: }
374: */
375: } else {
376: URL url = new URL(urlName);
377: handler = new HessianProxy(this , url);
378: }
379:
380: return Proxy.newProxyInstance(loader, new Class[] { api,
381: HessianRemoteObject.class }, handler);
382: }
383:
384: public AbstractHessianInput getHessianInput(InputStream is) {
385: AbstractHessianInput in;
386:
387: if (_isDebug)
388: is = new HessianDebugInputStream(is, new PrintWriter(
389: System.out));
390:
391: in = new Hessian2Input(is);
392:
393: in.setRemoteResolver(getRemoteResolver());
394:
395: in.setSerializerFactory(getSerializerFactory());
396:
397: return in;
398: }
399:
400: public AbstractHessianOutput getHessianOutput(OutputStream os) {
401: AbstractHessianOutput out;
402:
403: if (_isHessian2Request)
404: out = new Hessian2Output(os);
405: else {
406: HessianOutput out1 = new HessianOutput(os);
407: out = out1;
408:
409: if (_isHessian2Reply)
410: out1.setVersion(2);
411: }
412:
413: out.setSerializerFactory(getSerializerFactory());
414:
415: return out;
416: }
417:
418: /**
419: * JNDI object factory so the proxy can be used as a resource.
420: */
421: public Object getObjectInstance(Object obj, Name name,
422: Context nameCtx, Hashtable<?, ?> environment)
423: throws Exception {
424: Reference ref = (Reference) obj;
425:
426: String api = null;
427: String url = null;
428: String user = null;
429: String password = null;
430:
431: for (int i = 0; i < ref.size(); i++) {
432: RefAddr addr = ref.get(i);
433:
434: String type = addr.getType();
435: String value = (String) addr.getContent();
436:
437: if (type.equals("type"))
438: api = value;
439: else if (type.equals("url"))
440: url = value;
441: else if (type.equals("user"))
442: setUser(value);
443: else if (type.equals("password"))
444: setPassword(value);
445: }
446:
447: if (url == null)
448: throw new NamingException(
449: "`url' must be configured for HessianProxyFactory.");
450: // XXX: could use meta protocol to grab this
451: if (api == null)
452: throw new NamingException(
453: "`type' must be configured for HessianProxyFactory.");
454:
455: ClassLoader loader = Thread.currentThread()
456: .getContextClassLoader();
457: Class apiClass = Class.forName(api, false, loader);
458:
459: return create(apiClass, url);
460: }
461:
462: /**
463: * Creates the Base64 value.
464: */
465: private String base64(String value) {
466: StringBuffer cb = new StringBuffer();
467:
468: int i = 0;
469: for (i = 0; i + 2 < value.length(); i += 3) {
470: long chunk = (int) value.charAt(i);
471: chunk = (chunk << 8) + (int) value.charAt(i + 1);
472: chunk = (chunk << 8) + (int) value.charAt(i + 2);
473:
474: cb.append(encode(chunk >> 18));
475: cb.append(encode(chunk >> 12));
476: cb.append(encode(chunk >> 6));
477: cb.append(encode(chunk));
478: }
479:
480: if (i + 1 < value.length()) {
481: long chunk = (int) value.charAt(i);
482: chunk = (chunk << 8) + (int) value.charAt(i + 1);
483: chunk <<= 8;
484:
485: cb.append(encode(chunk >> 18));
486: cb.append(encode(chunk >> 12));
487: cb.append(encode(chunk >> 6));
488: cb.append('=');
489: } else if (i < value.length()) {
490: long chunk = (int) value.charAt(i);
491: chunk <<= 16;
492:
493: cb.append(encode(chunk >> 18));
494: cb.append(encode(chunk >> 12));
495: cb.append('=');
496: cb.append('=');
497: }
498:
499: return cb.toString();
500: }
501:
502: public static char encode(long d) {
503: d &= 0x3f;
504: if (d < 26)
505: return (char) (d + 'A');
506: else if (d < 52)
507: return (char) (d + 'a' - 26);
508: else if (d < 62)
509: return (char) (d + '0' - 52);
510: else if (d == 62)
511: return '+';
512: else
513: return '/';
514: }
515: }
|