KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > easybeans > component > smartclient > client > AskingClassLoader


1 /**
2  * EasyBeans
3  * Copyright (C) 2006 Bull S.A.S.
4  * Contact: easybeans@objectweb.org
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19  * USA
20  *
21  * --------------------------------------------------------------------------
22  * $Id:$
23  * --------------------------------------------------------------------------
24  */

25
26 package org.objectweb.easybeans.component.smartclient.client;
27
28 import static org.objectweb.easybeans.component.smartclient.api.ProtocolConstants.PROTOCOL_VERSION;
29
30 import java.io.File JavaDoc;
31 import java.io.FileOutputStream JavaDoc;
32 import java.io.IOException JavaDoc;
33 import java.net.InetSocketAddress JavaDoc;
34 import java.net.URL JavaDoc;
35 import java.net.URLClassLoader JavaDoc;
36 import java.nio.ByteBuffer JavaDoc;
37 import java.nio.channels.SocketChannel JavaDoc;
38 import java.util.logging.Level JavaDoc;
39 import java.util.logging.Logger JavaDoc;
40
41 import org.objectweb.easybeans.component.smartclient.api.Message;
42 import org.objectweb.easybeans.component.smartclient.api.ProtocolConstants;
43 import org.objectweb.easybeans.component.smartclient.message.ClassAnswer;
44 import org.objectweb.easybeans.component.smartclient.message.ClassNotFound;
45 import org.objectweb.easybeans.component.smartclient.message.ClassRequest;
46 import org.objectweb.easybeans.component.smartclient.message.ProviderURLAnswer;
47 import org.objectweb.easybeans.component.smartclient.message.ProviderURLRequest;
48 import org.objectweb.easybeans.component.smartclient.message.ResourceAnswer;
49 import org.objectweb.easybeans.component.smartclient.message.ResourceRequest;
50
51 /**
52  * ClassLoader that is used and that ask the EasyBeans remote server.
53  * @author Florent Benoit
54  */

55 public class AskingClassLoader extends URLClassLoader JavaDoc {
56
57     /**
58      * Use the JDK logger (to avoid any dependency).
59      */

60     private static Logger JavaDoc logger = Logger.getLogger(AskingClassLoader.class.getName());
61
62     /**
63      * Default buffer size.
64      */

65     private static final int DEFAULT_BUFFER_SIZE = 1024;
66
67     /**
68      * Socket adress used to connect.
69      */

70     private InetSocketAddress JavaDoc socketAddress = null;
71
72     /**
73      * Number of classes downloaded.
74      */

75     private static int nbClasses = 0;
76
77     /**
78      * Number of resources downloaded.
79      */

80     private static int nbResources = 0;
81
82     /**
83      * Number of bytes downloaded.
84      */

85     private static long nbBytes = 0;
86
87     /**
88      * All the time it took to ask server.
89      */

90     private static long timeToDownload = 0;
91
92     /**
93      * Creates a new classloader by using an empty URL.
94      * @param host the remote host to connect.
95      * @param portNumber the port number for the protocol.
96      */

97     public AskingClassLoader(final String JavaDoc host, final int portNumber) {
98         super(new URL JavaDoc[0]);
99
100         // Setup socket address
101
socketAddress = new InetSocketAddress JavaDoc(host, portNumber);
102
103         // Add hook for shutdown
104
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
105
106     }
107
108     /**
109      * Gets a channel to communicate with the server.
110      * @return a socket channel.
111      */

112     private SocketChannel JavaDoc getChannel() {
113         SocketChannel JavaDoc channel = null;
114
115         // open
116
try {
117             channel = SocketChannel.open();
118         } catch (IOException JavaDoc e) {
119             cleanChannel(channel);
120             throw new IllegalStateException JavaDoc("Cannot open a channel", e);
121         }
122
123         // Connect
124
try {
125             channel.connect(socketAddress);
126         } catch (IOException JavaDoc e) {
127             cleanChannel(channel);
128             throw new IllegalStateException JavaDoc("Cannot connect the channel", e);
129         }
130
131         return channel;
132     }
133
134     /**
135      * Cleanup the channel if there was a failure.
136      * @param channel the channel to cleanup.
137      */

138     private void cleanChannel(final SocketChannel JavaDoc channel) {
139         if (channel != null) {
140             try {
141                 channel.close();
142             } catch (IOException JavaDoc e) {
143                 logger.log(Level.FINE, "Cannot close the given channel", e);
144             }
145         }
146     }
147
148     /**
149      * Sends the given message on the given channel.
150      * @param message the message to send
151      * @param channel the channel used to send the message.
152      * @return the bytebuffer containing the answer (to analyze)
153      */

154     public ByteBuffer JavaDoc sendRequest(final Message message, final SocketChannel JavaDoc channel) {
155         // Send request
156
try {
157             channel.write(message.getMessage());
158         } catch (IOException JavaDoc e) {
159             cleanChannel(channel);
160             throw new IllegalStateException JavaDoc("Cannot send the given message '" + message + "'.", e);
161         }
162
163         // Read response
164
ByteBuffer JavaDoc buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);
165
166         ByteBuffer JavaDoc completeBuffer = null;
167         try {
168             int length = 0;
169             boolean finished = false;
170             while (!finished && (channel.read(buffer)) != -1) {
171                 // can read header
172
if (buffer.position() >= Message.HEADER_SIZE) {
173                     // Got length, create buffer
174
if (completeBuffer == null) {
175                         length = buffer.getInt(2);
176                         // Size + default buffer size so the copy from current
177
// buffer work all the time
178
completeBuffer = ByteBuffer.allocate(Message.HEADER_SIZE + length + DEFAULT_BUFFER_SIZE);
179
180                     }
181                 }
182                 // Append all read data into completeBuffer
183
buffer.flip();
184                 completeBuffer.put(buffer);
185
186                 // clear for next time
187
buffer.clear();
188
189                 if (completeBuffer.position() >= Message.HEADER_SIZE + length) {
190                     completeBuffer.limit(Message.HEADER_SIZE + length);
191                     // Skip Header, got OpCode, now create function
192
completeBuffer.position(Message.HEADER_SIZE);
193                     finished = true;
194                     break;
195                 }
196             }
197         } catch (Exception JavaDoc e) {
198             cleanChannel(channel);
199             throw new IllegalStateException JavaDoc("Cannot read the answer from the server.", e);
200         }
201
202         return completeBuffer;
203
204     }
205
206     /**
207      * Finds and loads the class with the specified name from the URL search
208      * path.<br>
209      * If the super classloader doesn't find the class, it ask the remote server
210      * to download the class
211      * @param name the name of the class
212      * @return the resulting class
213      * @exception ClassNotFoundException if the class could not be found
214      */

215     @Override JavaDoc
216     protected synchronized Class JavaDoc<?> findClass(final String JavaDoc name) throws ClassNotFoundException JavaDoc {
217         // search super classloader
218
Class JavaDoc<?> clazz = null;
219
220         try {
221             super.findClass(name);
222         } catch (ClassNotFoundException JavaDoc cnfe) {
223             SocketChannel JavaDoc channel = null;
224             try {
225                 long tStart = System.currentTimeMillis();
226                 // Get channel
227
channel = getChannel();
228                 ByteBuffer JavaDoc answerBuffer = sendRequest(new ClassRequest(name), channel);
229
230                 // Gets opCode
231
byte opCode = getOpCode(answerBuffer, channel);
232
233                 // stats
234
timeToDownload = timeToDownload + (System.currentTimeMillis() - tStart);
235
236                 // Switch :
237
switch (opCode) {
238                 case ProtocolConstants.CLASS_ANSWER:
239                     ClassAnswer classAnswer = new ClassAnswer(answerBuffer);
240                     try {
241                         clazz = loadClass(name, classAnswer.getByteCode());
242                     } catch (IOException JavaDoc e) {
243                         throw new ClassNotFoundException JavaDoc("Cannot find the class", e);
244                     }
245                     nbClasses++;
246                     nbBytes = nbBytes + classAnswer.getByteCode().length;
247                     break;
248                 case ProtocolConstants.CLASS_NOT_FOUND:
249                     ClassNotFound classNotFound = new ClassNotFound(answerBuffer);
250                     throw new ClassNotFoundException JavaDoc("The class '" + classNotFound.getName()
251                             + "' was not found on the remote side");
252                 default:
253                     throw new ClassNotFoundException JavaDoc("Invalid opCode '" + opCode + "' received");
254                 }
255             } finally {
256                 // cleanup
257
cleanChannel(channel);
258             }
259         }
260
261         return clazz;
262
263     }
264
265     /**
266      * Ask and return the remote PROVIDER_URL in order to connect with RMI.
267      * @return a string with the PROVIDER_URL value.
268      */

269     public String JavaDoc getProviderURL() {
270         String JavaDoc providerURL = null;
271         SocketChannel JavaDoc channel = null;
272         try {
273             long tStart = System.currentTimeMillis();
274             // Get channel
275
channel = getChannel();
276             ByteBuffer JavaDoc answerBuffer = sendRequest(new ProviderURLRequest(), channel);
277
278             // Gets opCode
279
byte opCode = getOpCode(answerBuffer, channel);
280
281             // stats
282
timeToDownload = timeToDownload + (System.currentTimeMillis() - tStart);
283
284             // Switch :
285
switch (opCode) {
286             case ProtocolConstants.PROVIDER_URL_ANSWER:
287                 ProviderURLAnswer providerURLAnswer = new ProviderURLAnswer(answerBuffer);
288                 providerURL = providerURLAnswer.getProviderURL();
289                 break;
290             default:
291                 throw new IllegalStateException JavaDoc("Invalid opCode '" + opCode + "' received");
292             }
293         } finally {
294             // cleanup
295
cleanChannel(channel);
296         }
297         return providerURL;
298     }
299
300     /**
301      * Gets the operation code from the current buffer.
302      * @param buffer the buffer to analyze.
303      * @param channel the channel which is use for the exchange.
304      * @return the operation code.
305      */

306     private byte getOpCode(final ByteBuffer JavaDoc buffer, final SocketChannel JavaDoc channel) {
307         // Check if it is a protocol that we manage
308
byte version = buffer.get(0);
309         if (version != PROTOCOL_VERSION) {
310             cleanChannel(channel);
311             throw new IllegalStateException JavaDoc("Invalid protocol version : waiting '" + PROTOCOL_VERSION + "', got '" + version
312                     + "'.");
313         }
314
315         // Get operation asked by client
316
byte opCode = buffer.get(1);
317         // Length
318
int length = buffer.getInt(2);
319         if (length < 0) {
320             cleanChannel(channel);
321             throw new IllegalStateException JavaDoc("Invalid length for client '" + length + "'.");
322         }
323         return opCode;
324     }
325
326     /**
327      * Finds the resource with the specified name on the URL search path. <br>
328      * If resource is not found locally, search on the remote side.
329      * @param name the name of the resource
330      * @return a <code>URL</code> for the resource, or <code>null</code> if
331      * the resource could not be found.
332      */

333     @Override JavaDoc
334     public synchronized URL JavaDoc findResource(final String JavaDoc name) {
335         URL JavaDoc url = null;
336         url = super.findResource(name);
337
338         if (url != null) {
339             return url;
340         }
341
342         if (name.startsWith("META-INF")) {
343             return null;
344         }
345
346         SocketChannel JavaDoc channel = null;
347         try {
348             long tStart = System.currentTimeMillis();
349
350             // Get channel
351
channel = getChannel();
352             ByteBuffer JavaDoc answerBuffer = sendRequest(new ResourceRequest(name), channel);
353
354             // Gets opCode
355
byte opCode = getOpCode(answerBuffer, channel);
356
357             // stats
358
timeToDownload = timeToDownload + (System.currentTimeMillis() - tStart);
359
360             // Switch :
361
switch (opCode) {
362             case ProtocolConstants.RESOURCE_ANSWER:
363                 ResourceAnswer resourceAnswer = new ResourceAnswer(answerBuffer);
364                 String JavaDoc resourceName = resourceAnswer.getResourceName();
365                 byte[] bytes = resourceAnswer.getBytes();
366
367                 nbResources++;
368                 nbBytes = nbBytes + resourceAnswer.getBytes().length;
369
370                 File JavaDoc fConfDir = new File JavaDoc(System.getProperty("java.io.tmpdir") + File.separator + "easybeans-smart");
371                 if (!fConfDir.exists()) {
372                     fConfDir.mkdir();
373                 }
374
375                 // convert / into File.separator
376
String JavaDoc[] tokens = resourceName.split("/");
377                 StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
378                 for (String JavaDoc token : tokens) {
379                     if (sb.length() > 0) {
380                         sb.append(File.separator);
381                     }
382                     sb.append(token);
383                 }
384
385                 // Create parent dir if does not exist
386
File JavaDoc urlFile = new File JavaDoc(fConfDir, sb.toString());
387                 if (!urlFile.getParentFile().exists()) {
388                     urlFile.getParentFile().mkdir();
389                 }
390
391                 // dump stream
392
FileOutputStream JavaDoc fos = new FileOutputStream JavaDoc(urlFile);
393                 fos.write(bytes);
394                 fos.close();
395                 url = urlFile.toURI().toURL();
396                 break;
397             case ProtocolConstants.RESOURCE_NOT_FOUND:
398                 url = null;
399                 break;
400             default:
401                 throw new IllegalStateException JavaDoc("Invalid opCode '" + opCode + "' received");
402             }
403         } catch (Exception JavaDoc e) {
404             logger.log(Level.SEVERE, "Cannot handle : findResource '" + name + "'", e);
405         } finally {
406             // cleanup
407
cleanChannel(channel);
408         }
409
410         return url;
411     }
412
413     /**
414      * Defines a class by loading the bytecode for the given class name.
415      * @param className the name of the class to define
416      * @param bytecode the bytecode of the class
417      * @return the class that was defined
418      * @throws IOException if the class cannot be defined.
419      */

420
421     private Class JavaDoc loadClass(final String JavaDoc className, final byte[] bytecode) throws IOException JavaDoc {
422         // override classDefine (as it is protected) and define the class.
423
Class JavaDoc clazz = null;
424         try {
425             ClassLoader JavaDoc loader = this;
426             Class JavaDoc cls = Class.forName("java.lang.ClassLoader");
427             java.lang.reflect.Method JavaDoc method = cls.getDeclaredMethod("defineClass", new Class JavaDoc[] {String JavaDoc.class, byte[].class,
428                     int.class, int.class });
429
430             // protected method invocaton
431
method.setAccessible(true);
432             try {
433                 Object JavaDoc[] args = new Object JavaDoc[] {className, bytecode, new Integer JavaDoc(0), new Integer JavaDoc(bytecode.length) };
434                 clazz = (Class JavaDoc) method.invoke(loader, args);
435             } finally {
436                 method.setAccessible(false);
437             }
438         } catch (Exception JavaDoc e) {
439             IOException JavaDoc ioe = new IOException JavaDoc("Cannt define class with name '" + className + "'.");
440             ioe.initCause(e);
441             throw ioe;
442         }
443         return clazz;
444     }
445
446     /**
447      * Hook that is called when process is going to shutdown.
448      * @author Florent Benoit
449      */

450     static class ShutdownHook extends Thread JavaDoc {
451
452         /**
453          * Display stats.
454          */

455         @Override JavaDoc
456         public void run() {
457             // display statistics (use sysout)
458
System.out.println("Downloaded '" + nbClasses + "' classes, '" + nbResources + "' resources for a total of '"
459                     + nbBytes + "' bytes and it took '" + timeToDownload + "' ms.");
460         }
461     }
462 }
463
Popular Tags