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,
115: ObjectFactory {
116: private BurlapRemoteResolver _resolver;
117:
118: private String _user;
119: private String _password;
120: private String _basicAuth;
121:
122: private boolean _isOverloadEnabled = false;
123:
124: /**
125: * Creates the new proxy factory.
126: */
127: public BurlapProxyFactory() {
128: _resolver = new BurlapProxyResolver(this );
129: }
130:
131: /**
132: * Sets the user.
133: */
134: public void setUser(String user) {
135: _user = user;
136: _basicAuth = null;
137: }
138:
139: /**
140: * Sets the password.
141: */
142: public void setPassword(String password) {
143: _password = password;
144: _basicAuth = null;
145: }
146:
147: /**
148: * Returns true if overloaded methods are allowed (using mangling)
149: */
150: public boolean isOverloadEnabled() {
151: return _isOverloadEnabled;
152: }
153:
154: /**
155: * set true if overloaded methods are allowed (using mangling)
156: */
157: public void setOverloadEnabled(boolean isOverloadEnabled) {
158: _isOverloadEnabled = isOverloadEnabled;
159: }
160:
161: /**
162: * Returns the remote resolver.
163: */
164: public BurlapRemoteResolver getRemoteResolver() {
165: return _resolver;
166: }
167:
168: /**
169: * Creates the URL connection.
170: */
171: protected URLConnection openConnection(URL url) throws IOException {
172: URLConnection conn = url.openConnection();
173:
174: conn.setDoOutput(true);
175:
176: if (_basicAuth != null)
177: conn.setRequestProperty("Authorization", _basicAuth);
178: else if (_user != null && _password != null) {
179: _basicAuth = "Basic " + base64(_user + ":" + _password);
180: conn.setRequestProperty("Authorization", _basicAuth);
181: }
182:
183: return conn;
184: }
185:
186: /**
187: * Creates a new proxy with the specified URL. The API class uses
188: * the java.api.class value from _hessian_
189: *
190: * @param url the URL where the client object is located.
191: *
192: * @return a proxy to the object with the specified interface.
193: */
194: public Object create(String url) throws MalformedURLException,
195: ClassNotFoundException {
196: BurlapMetaInfoAPI metaInfo;
197:
198: metaInfo = (BurlapMetaInfoAPI) create(BurlapMetaInfoAPI.class,
199: url);
200:
201: String apiClassName = (String) metaInfo
202: ._burlap_getAttribute("java.api.class");
203:
204: if (apiClassName == null)
205: throw new BurlapRuntimeException(url
206: + " has an unknown api.");
207:
208: ClassLoader loader = Thread.currentThread()
209: .getContextClassLoader();
210:
211: Class apiClass = Class.forName(apiClassName, false, loader);
212:
213: return create(apiClass, url);
214: }
215:
216: /**
217: * Creates a new proxy with the specified URL. The returned object
218: * is a proxy with the interface specified by api.
219: *
220: * <pre>
221: * String url = "http://localhost:8080/ejb/hello");
222: * HelloHome hello = (HelloHome) factory.create(HelloHome.class, url);
223: * </pre>
224: *
225: * @param api the interface the proxy class needs to implement
226: * @param url the URL where the client object is located.
227: *
228: * @return a proxy to the object with the specified interface.
229: */
230: public Object create(Class api, String urlName)
231: throws MalformedURLException {
232: if (api == null)
233: throw new NullPointerException();
234:
235: URL url = new URL(urlName);
236:
237: try {
238: // clear old keepalive connections
239: HttpURLConnection conn = (HttpURLConnection) url
240: .openConnection();
241:
242: conn.setRequestProperty("Connection", "close");
243:
244: InputStream is = conn.getInputStream();
245:
246: is.close();
247:
248: conn.disconnect();
249: } catch (IOException e) {
250: }
251:
252: BurlapProxy handler = new BurlapProxy(this , url);
253:
254: return Proxy.newProxyInstance(api.getClassLoader(),
255: new Class[] { api, BurlapRemoteObject.class }, handler);
256: }
257:
258: public AbstractBurlapInput getBurlapInput(InputStream is) {
259: AbstractBurlapInput in = new BurlapInput(is);
260: in.setRemoteResolver(getRemoteResolver());
261:
262: return in;
263: }
264:
265: public BurlapOutput getBurlapOutput(OutputStream os) {
266: BurlapOutput out = new BurlapOutput(os);
267:
268: return out;
269: }
270:
271: /**
272: * JNDI object factory so the proxy can be used as a resource.
273: */
274: public Object getObjectInstance(Object obj, Name name,
275: Context nameCtx, Hashtable<?, ?> environment)
276: throws Exception {
277: Reference ref = (Reference) obj;
278:
279: String api = null;
280: String url = null;
281: String user = null;
282: String password = null;
283:
284: for (int i = 0; i < ref.size(); i++) {
285: RefAddr addr = ref.get(i);
286:
287: String type = addr.getType();
288: String value = (String) addr.getContent();
289:
290: if (type.equals("type"))
291: api = value;
292: else if (type.equals("url"))
293: url = value;
294: else if (type.equals("user"))
295: setUser(value);
296: else if (type.equals("password"))
297: setPassword(value);
298: }
299:
300: if (url == null)
301: throw new NamingException(
302: "`url' must be configured for BurlapProxyFactory.");
303: // XXX: could use meta protocol to grab this
304: if (api == null)
305: throw new NamingException(
306: "`type' must be configured for BurlapProxyFactory.");
307:
308: ClassLoader loader = Thread.currentThread()
309: .getContextClassLoader();
310: Class apiClass = Class.forName(api, false, loader);
311:
312: return create(apiClass, url);
313: }
314:
315: /**
316: * Creates the Base64 value.
317: */
318: private String base64(String value) {
319: StringBuffer cb = new StringBuffer();
320:
321: int i = 0;
322: for (i = 0; i + 2 < value.length(); i += 3) {
323: long chunk = (int) value.charAt(i);
324: chunk = (chunk << 8) + (int) value.charAt(i + 1);
325: chunk = (chunk << 8) + (int) value.charAt(i + 2);
326:
327: cb.append(encode(chunk >> 18));
328: cb.append(encode(chunk >> 12));
329: cb.append(encode(chunk >> 6));
330: cb.append(encode(chunk));
331: }
332:
333: if (i + 1 < value.length()) {
334: long chunk = (int) value.charAt(i);
335: chunk = (chunk << 8) + (int) value.charAt(i + 1);
336: chunk <<= 8;
337:
338: cb.append(encode(chunk >> 18));
339: cb.append(encode(chunk >> 12));
340: cb.append(encode(chunk >> 6));
341: cb.append('=');
342: } else if (i < value.length()) {
343: long chunk = (int) value.charAt(i);
344: chunk <<= 16;
345:
346: cb.append(encode(chunk >> 18));
347: cb.append(encode(chunk >> 12));
348: cb.append('=');
349: cb.append('=');
350: }
351:
352: return cb.toString();
353: }
354:
355: public static char encode(long d) {
356: d &= 0x3f;
357: if (d < 26)
358: return (char) (d + 'A');
359: else if (d < 52)
360: return (char) (d + 'a' - 26);
361: else if (d < 62)
362: return (char) (d + '0' - 52);
363: else if (d == 62)
364: return '+';
365: else
366: return '/';
367: }
368: }
|