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     * &lt;reference>
094     *   &lt;name>hessian/hello&lt;/name>
095     *   &lt;factory>com.caucho.hessian.client.HessianProxyFactory&lt;/factory>
096     *   &lt;init url="http://localhost:8080/ejb/hello"/>
097     *         type="test.HelloHome"/>
098     * &lt;/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