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