001: /**
002: * EasyBeans
003: * Copyright (C) 2006 Bull S.A.S.
004: * Contact: easybeans@ow2.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: AskingClassLoader.java 2003 2007-10-23 13:26:16Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.ow2.easybeans.component.smartclient.client;
025:
026: import static org.ow2.easybeans.component.smartclient.api.ProtocolConstants.PROTOCOL_VERSION;
027:
028: import java.io.File;
029: import java.io.FileOutputStream;
030: import java.io.IOException;
031: import java.net.InetSocketAddress;
032: import java.net.URL;
033: import java.net.URLClassLoader;
034: import java.nio.ByteBuffer;
035: import java.nio.channels.SocketChannel;
036: import java.util.logging.Level;
037: import java.util.logging.Logger;
038:
039: import org.ow2.easybeans.component.smartclient.api.Message;
040: import org.ow2.easybeans.component.smartclient.api.ProtocolConstants;
041: import org.ow2.easybeans.component.smartclient.message.ClassAnswer;
042: import org.ow2.easybeans.component.smartclient.message.ClassNotFound;
043: import org.ow2.easybeans.component.smartclient.message.ClassRequest;
044: import org.ow2.easybeans.component.smartclient.message.ProviderURLAnswer;
045: import org.ow2.easybeans.component.smartclient.message.ProviderURLRequest;
046: import org.ow2.easybeans.component.smartclient.message.ResourceAnswer;
047: import org.ow2.easybeans.component.smartclient.message.ResourceRequest;
048:
049: /**
050: * ClassLoader that is used and that ask the EasyBeans remote server.
051: * @author Florent Benoit
052: */
053: public class AskingClassLoader extends URLClassLoader {
054:
055: /**
056: * Use the JDK logger (to avoid any dependency).
057: */
058: private static Logger logger = Logger
059: .getLogger(AskingClassLoader.class.getName());
060:
061: /**
062: * Default buffer size.
063: */
064: private static final int DEFAULT_BUFFER_SIZE = 1024;
065:
066: /**
067: * Socket adress used to connect.
068: */
069: private InetSocketAddress socketAddress = null;
070:
071: /**
072: * Number of classes downloaded.
073: */
074: private static int nbClasses = 0;
075:
076: /**
077: * Number of resources downloaded.
078: */
079: private static int nbResources = 0;
080:
081: /**
082: * Number of bytes downloaded.
083: */
084: private static long nbBytes = 0;
085:
086: /**
087: * All the time it took to ask server.
088: */
089: private static long timeToDownload = 0;
090:
091: /**
092: * Creates a new classloader by using an empty URL.
093: * @param host the remote host to connect.
094: * @param portNumber the port number for the protocol.
095: */
096: public AskingClassLoader(final String host, final int portNumber) {
097: super (new URL[0], Thread.currentThread()
098: .getContextClassLoader());
099:
100: // Setup socket address
101: socketAddress = new InetSocketAddress(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 getChannel() {
113: SocketChannel channel = null;
114:
115: // open
116: try {
117: channel = SocketChannel.open();
118: } catch (IOException e) {
119: cleanChannel(channel);
120: throw new IllegalStateException("Cannot open a channel", e);
121: }
122:
123: // Connect
124: try {
125: channel.connect(socketAddress);
126: } catch (IOException e) {
127: cleanChannel(channel);
128: throw new IllegalStateException(
129: "Cannot connect the channel", e);
130: }
131:
132: return channel;
133: }
134:
135: /**
136: * Cleanup the channel if there was a failure.
137: * @param channel the channel to cleanup.
138: */
139: private void cleanChannel(final SocketChannel channel) {
140: if (channel != null) {
141: try {
142: channel.close();
143: } catch (IOException e) {
144: logger.log(Level.FINE,
145: "Cannot close the given channel", e);
146: }
147: }
148: }
149:
150: /**
151: * Sends the given message on the given channel.
152: * @param message the message to send
153: * @param channel the channel used to send the message.
154: * @return the bytebuffer containing the answer (to analyze)
155: */
156: public ByteBuffer sendRequest(final Message message,
157: final SocketChannel channel) {
158: // Send request
159: try {
160: channel.write(message.getMessage());
161: } catch (IOException e) {
162: cleanChannel(channel);
163: throw new IllegalStateException(
164: "Cannot send the given message '" + message + "'.",
165: e);
166: }
167:
168: // Read response
169: ByteBuffer buffer = ByteBuffer
170: .allocateDirect(DEFAULT_BUFFER_SIZE);
171:
172: ByteBuffer completeBuffer = null;
173: try {
174: int length = 0;
175: boolean finished = false;
176: while (!finished && (channel.read(buffer)) != -1) {
177: // can read header
178: if (buffer.position() >= Message.HEADER_SIZE) {
179: // Got length, create buffer
180: if (completeBuffer == null) {
181: length = buffer.getInt(2);
182: // Size + default buffer size so the copy from current
183: // buffer work all the time
184: completeBuffer = ByteBuffer
185: .allocate(Message.HEADER_SIZE + length
186: + DEFAULT_BUFFER_SIZE);
187:
188: }
189: }
190: // Append all read data into completeBuffer
191: buffer.flip();
192: completeBuffer.put(buffer);
193:
194: // clear for next time
195: buffer.clear();
196:
197: if (completeBuffer.position() >= Message.HEADER_SIZE
198: + length) {
199: completeBuffer.limit(Message.HEADER_SIZE + length);
200: // Skip Header, got OpCode, now create function
201: completeBuffer.position(Message.HEADER_SIZE);
202: finished = true;
203: break;
204: }
205: }
206: } catch (Exception e) {
207: cleanChannel(channel);
208: throw new IllegalStateException(
209: "Cannot read the answer from the server.", e);
210: }
211:
212: return completeBuffer;
213:
214: }
215:
216: /**
217: * Finds and loads the class with the specified name from the URL search
218: * path.<br>
219: * If the super classloader doesn't find the class, it ask the remote server
220: * to download the class
221: * @param name the name of the class
222: * @return the resulting class
223: * @exception ClassNotFoundException if the class could not be found
224: */
225: @Override
226: protected synchronized Class<?> findClass(final String name)
227: throws ClassNotFoundException {
228: // search super classloader
229: Class<?> clazz = null;
230:
231: try {
232: super .findClass(name);
233: } catch (ClassNotFoundException cnfe) {
234: SocketChannel channel = null;
235: try {
236: long tStart = System.currentTimeMillis();
237: // Get channel
238: channel = getChannel();
239: ByteBuffer answerBuffer = sendRequest(new ClassRequest(
240: name), channel);
241:
242: // Gets opCode
243: byte opCode = getOpCode(answerBuffer, channel);
244:
245: // stats
246: timeToDownload = timeToDownload
247: + (System.currentTimeMillis() - tStart);
248:
249: // Switch :
250: switch (opCode) {
251: case ProtocolConstants.CLASS_ANSWER:
252: ClassAnswer classAnswer = new ClassAnswer(
253: answerBuffer);
254: try {
255: clazz = loadClass(name, classAnswer
256: .getByteCode());
257: } catch (IOException e) {
258: throw new ClassNotFoundException(
259: "Cannot find the class", e);
260: }
261: nbClasses++;
262: nbBytes = nbBytes
263: + classAnswer.getByteCode().length;
264: // display statistics (use sysout)
265: if (Boolean.getBoolean("smart.debug.verbose")) {
266: System.out.println("Downloaded class '" + name
267: + "'.");
268: }
269: break;
270: case ProtocolConstants.CLASS_NOT_FOUND:
271: ClassNotFound classNotFound = new ClassNotFound(
272: answerBuffer);
273: throw new ClassNotFoundException("The class '"
274: + classNotFound.getName()
275: + "' was not found on the remote side");
276: default:
277: throw new ClassNotFoundException("Invalid opCode '"
278: + opCode + "' received");
279: }
280: } finally {
281: // cleanup
282: cleanChannel(channel);
283: }
284: }
285:
286: return clazz;
287:
288: }
289:
290: /**
291: * Ask and return the remote PROVIDER_URL in order to connect with RMI.
292: * @return a string with the PROVIDER_URL value.
293: */
294: public String getProviderURL() {
295: String providerURL = null;
296: SocketChannel channel = null;
297: try {
298: long tStart = System.currentTimeMillis();
299: // Get channel
300: channel = getChannel();
301: ByteBuffer answerBuffer = sendRequest(
302: new ProviderURLRequest(), channel);
303:
304: // Gets opCode
305: byte opCode = getOpCode(answerBuffer, channel);
306:
307: // stats
308: timeToDownload = timeToDownload
309: + (System.currentTimeMillis() - tStart);
310:
311: // Switch :
312: switch (opCode) {
313: case ProtocolConstants.PROVIDER_URL_ANSWER:
314: ProviderURLAnswer providerURLAnswer = new ProviderURLAnswer(
315: answerBuffer);
316: providerURL = providerURLAnswer.getProviderURL();
317: break;
318: default:
319: throw new IllegalStateException("Invalid opCode '"
320: + opCode + "' received");
321: }
322: } finally {
323: // cleanup
324: cleanChannel(channel);
325: }
326: return providerURL;
327: }
328:
329: /**
330: * Gets the operation code from the current buffer.
331: * @param buffer the buffer to analyze.
332: * @param channel the channel which is use for the exchange.
333: * @return the operation code.
334: */
335: private byte getOpCode(final ByteBuffer buffer,
336: final SocketChannel channel) {
337: if (buffer == null) {
338: throw new IllegalStateException("Empty buffer received");
339: }
340:
341: // Check if it is a protocol that we manage
342: byte version = buffer.get(0);
343: if (version != PROTOCOL_VERSION) {
344: cleanChannel(channel);
345: throw new IllegalStateException(
346: "Invalid protocol version : waiting '"
347: + PROTOCOL_VERSION + "', got '" + version
348: + "'.");
349: }
350:
351: // Get operation asked by client
352: byte opCode = buffer.get(1);
353: // Length
354: int length = buffer.getInt(2);
355: if (length < 0) {
356: cleanChannel(channel);
357: throw new IllegalStateException(
358: "Invalid length for client '" + length + "'.");
359: }
360: return opCode;
361: }
362:
363: /**
364: * Finds the resource with the specified name on the URL search path. <br>
365: * If resource is not found locally, search on the remote side.
366: * @param name the name of the resource
367: * @return a <code>URL</code> for the resource, or <code>null</code> if
368: * the resource could not be found.
369: */
370: @Override
371: public synchronized URL findResource(final String name) {
372: URL url = null;
373: url = super .findResource(name);
374:
375: if (url != null) {
376: return url;
377: }
378:
379: if (name.startsWith("META-INF")) {
380: return null;
381: }
382:
383: SocketChannel channel = null;
384: try {
385: long tStart = System.currentTimeMillis();
386:
387: // Get channel
388: channel = getChannel();
389: ByteBuffer answerBuffer = sendRequest(new ResourceRequest(
390: name), channel);
391:
392: // Gets opCode
393: byte opCode = getOpCode(answerBuffer, channel);
394:
395: // stats
396: timeToDownload = timeToDownload
397: + (System.currentTimeMillis() - tStart);
398:
399: // Switch :
400: switch (opCode) {
401: case ProtocolConstants.RESOURCE_ANSWER:
402: ResourceAnswer resourceAnswer = new ResourceAnswer(
403: answerBuffer);
404: String resourceName = resourceAnswer.getResourceName();
405: byte[] bytes = resourceAnswer.getBytes();
406:
407: nbResources++;
408: nbBytes = nbBytes + resourceAnswer.getBytes().length;
409:
410: File fConfDir = new File(System
411: .getProperty("java.io.tmpdir")
412: + File.separator + "easybeans-smart");
413: if (!fConfDir.exists()) {
414: fConfDir.mkdir();
415: }
416:
417: // convert / into File.separator
418: String[] tokens = resourceName.split("/");
419: StringBuilder sb = new StringBuilder();
420: for (String token : tokens) {
421: if (sb.length() > 0) {
422: sb.append(File.separator);
423: }
424: sb.append(token);
425: }
426:
427: // Create parent dir if does not exist
428: File urlFile = new File(fConfDir, sb.toString());
429: if (!urlFile.getParentFile().exists()) {
430: urlFile.getParentFile().mkdir();
431: }
432:
433: // dump stream
434: FileOutputStream fos = new FileOutputStream(urlFile);
435: fos.write(bytes);
436: fos.close();
437: url = urlFile.toURI().toURL();
438: break;
439: case ProtocolConstants.RESOURCE_NOT_FOUND:
440: url = null;
441: break;
442: default:
443: throw new IllegalStateException("Invalid opCode '"
444: + opCode + "' received");
445: }
446: } catch (Exception e) {
447: logger.log(Level.SEVERE, "Cannot handle : findResource '"
448: + name + "'", e);
449: } finally {
450: // cleanup
451: cleanChannel(channel);
452: }
453:
454: return url;
455: }
456:
457: /**
458: * Defines a class by loading the bytecode for the given class name.
459: * @param className the name of the class to define
460: * @param bytecode the bytecode of the class
461: * @return the class that was defined
462: * @throws IOException if the class cannot be defined.
463: */
464:
465: private Class<?> loadClass(final String className,
466: final byte[] bytecode) throws IOException {
467: // override classDefine (as it is protected) and define the class.
468: Class<?> clazz = null;
469: try {
470: ClassLoader loader = this ;
471: java.lang.reflect.Method method = ClassLoader.class
472: .getDeclaredMethod("defineClass", String.class,
473: byte[].class, int.class, int.class);
474:
475: // protected method invocaton
476: method.setAccessible(true);
477: try {
478: clazz = (Class<?>) method.invoke(loader, className,
479: bytecode, Integer.valueOf(0), Integer
480: .valueOf(bytecode.length));
481: } finally {
482: method.setAccessible(false);
483: }
484: } catch (Exception e) {
485: IOException ioe = new IOException(
486: "Cannt define class with name '" + className + "'.");
487: ioe.initCause(e);
488: throw ioe;
489: }
490: return clazz;
491: }
492:
493: /**
494: * Hook that is called when process is going to shutdown.
495: * @author Florent Benoit
496: */
497: static class ShutdownHook extends Thread {
498:
499: /**
500: * Display stats.
501: */
502: @Override
503: public void run() {
504: // display statistics (use sysout)
505: if (Boolean.getBoolean("smart.debug")) {
506: System.out.println("Downloaded '" + nbClasses
507: + "' classes, '" + nbResources
508: + "' resources for a total of '" + nbBytes
509: + "' bytes and it took '" + timeToDownload
510: + "' ms.");
511: }
512: }
513: }
514: }
|