Package org.ow2.easybeans.component.smartclient.client

Source Code of org.ow2.easybeans.component.smartclient.client.AskingClassLoader

/**
* EasyBeans
* Copyright (C) 2006 Bull S.A.S.
* Contact: easybeans@ow2.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id: AskingClassLoader.java 5567 2010-09-14 13:33:05Z benoitf $
* --------------------------------------------------------------------------
*/

package org.ow2.easybeans.component.smartclient.client;

import static org.ow2.easybeans.component.smartclient.api.ProtocolConstants.PROTOCOL_VERSION;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.ow2.easybeans.component.smartclient.api.Message;
import org.ow2.easybeans.component.smartclient.api.ProtocolConstants;
import org.ow2.easybeans.component.smartclient.message.ClassAnswer;
import org.ow2.easybeans.component.smartclient.message.ClassNotFound;
import org.ow2.easybeans.component.smartclient.message.ClassRequest;
import org.ow2.easybeans.component.smartclient.message.ProviderURLAnswer;
import org.ow2.easybeans.component.smartclient.message.ProviderURLRequest;
import org.ow2.easybeans.component.smartclient.message.ResourceAnswer;
import org.ow2.easybeans.component.smartclient.message.ResourceRequest;

/**
* ClassLoader that is used and that ask the EasyBeans remote server.
* @author Florent Benoit
*/
public class AskingClassLoader extends URLClassLoader {

    /**
     * Use the JDK logger (to avoid any dependency).
     */
    private static Logger logger = Logger.getLogger(AskingClassLoader.class.getName());

    /**
     * Default buffer size.
     */
    private static final int DEFAULT_BUFFER_SIZE = 1024;

    /**
     * Socket adress used to connect.
     */
    private InetSocketAddress socketAddress = null;

    /**
     * Number of classes downloaded.
     */
    private static int nbClasses = 0;

    /**
     * Number of resources downloaded.
     */
    private static int nbResources = 0;

    /**
     * Number of bytes downloaded.
     */
    private static long nbBytes = 0;

    /**
     * All the time it took to ask server.
     */
    private static long timeToDownload = 0;

    /**
     * Directory used to store temporary resources that are downloaded.
     */
    private File tmpDirectory = null;


    /**
     * Creates a new classloader by using an empty URL.
     * @param host the remote host to connect.
     * @param portNumber the port number for the protocol.
     */
    public AskingClassLoader(final String host, final int portNumber) {
        this(host, portNumber, Thread.currentThread().getContextClassLoader(), new URL[0]);
    }


    /**
     * Creates a new classloader by using an empty URL.
     * @param host the remote host to connect.
     * @param portNumber the port number for the protocol.
     * @param urls the given URLs for this classloader
     */
    public AskingClassLoader(final String host, final int portNumber, final URL[] urls) {
        this(host, portNumber, Thread.currentThread().getContextClassLoader(), urls);
    }

    /**
     * Creates a new classloader by using an empty URL.
     * @param host the remote host to connect.
     * @param portNumber the port number for the protocol.
     * @param parentClassLoader the parent classloader
     * @param urls the given URLs for this classloader
     */
    public AskingClassLoader(final String host, final int portNumber, final ClassLoader parentClassLoader, final URL[] urls) {
        super(urls, parentClassLoader);

        // Create directory
        this.tmpDirectory = new File(System.getProperty("java.io.tmpdir") + File.separator + "easybeans-smart-"
                + System.getProperty("user.name") + "-" + cleanup(host) + "_" + portNumber);
        if (!this.tmpDirectory.exists()) {
            this.tmpDirectory.mkdir();
        }

        // Should be deleted when we shuthdown the JVM
        this.tmpDirectory.deleteOnExit();

        // Setup socket address
        this.socketAddress = new InetSocketAddress(host, portNumber);

        // Add hook for shutdown
        try {
            Runtime.getRuntime().addShutdownHook(new ShutdownHook(tmpDirectory));
        } catch (IllegalStateException e) {
            logger.log(Level.FINE, "Cannot add a new hook", e);
        }

    }

    /**
     * Gets a channel to communicate with the server.
     * @return a socket channel.
     */
    private SocketChannel getChannel() {
        SocketChannel channel = null;

        // open
        try {
            channel = SocketChannel.open();
        } catch (IOException e) {
            cleanChannel(channel);
            throw new IllegalStateException("Cannot open a channel", e);
        }

        // Connect
        try {
            channel.connect(this.socketAddress);
        } catch (IOException e) {
            cleanChannel(channel);
            throw new IllegalStateException("Cannot connect the channel", e);
        }

        return channel;
    }

    /**
     * Cleanup the channel if there was a failure.
     * @param channel the channel to cleanup.
     */
    private void cleanChannel(final SocketChannel channel) {
        if (channel != null) {
            try {
                channel.close();
            } catch (IOException e) {
                logger.log(Level.FINE, "Cannot close the given channel", e);
            }
        }
    }

    /**
     * Sends the given message on the given channel.
     * @param message the message to send
     * @param channel the channel used to send the message.
     * @return the bytebuffer containing the answer (to analyze)
     */
    public ByteBuffer sendRequest(final Message message, final SocketChannel channel) {
        // Send request
        try {
            channel.write(message.getMessage());
        } catch (IOException e) {
            cleanChannel(channel);
            throw new IllegalStateException("Cannot send the given message '" + message + "'.", e);
        }

        // Read response
        ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);

        ByteBuffer completeBuffer = null;
        try {
            int length = 0;
            boolean finished = false;
            while (!finished && (channel.read(buffer)) != -1) {
                // can read header
                if (buffer.position() >= Message.HEADER_SIZE) {
                    // Got length, create buffer
                    if (completeBuffer == null) {
                        length = buffer.getInt(2);
                        // Size + default buffer size so the copy from current
                        // buffer work all the time
                        completeBuffer = ByteBuffer.allocate(Message.HEADER_SIZE + length + DEFAULT_BUFFER_SIZE);

                    }
                }
                // Append all read data into completeBuffer
                buffer.flip();
                completeBuffer.put(buffer);

                // clear for next time
                buffer.clear();

                if (completeBuffer.position() >= Message.HEADER_SIZE + length) {
                    completeBuffer.limit(Message.HEADER_SIZE + length);
                    // Skip Header, got OpCode, now create function
                    completeBuffer.position(Message.HEADER_SIZE);
                    finished = true;
                    break;
                }
            }
        } catch (Exception e) {
            cleanChannel(channel);
            throw new IllegalStateException("Cannot read the answer from the server.", e);
        }

        return completeBuffer;

    }

    /**
     * Finds and loads the class with the specified name from the URL search
     * path.<br>
     * If the super classloader doesn't find the class, it ask the remote server
     * to download the class
     * @param name the name of the class
     * @return the resulting class
     * @exception ClassNotFoundException if the class could not be found
     */
    @Override
    protected synchronized Class<?> findClass(final String name) throws ClassNotFoundException {
        // search super classloader
        Class<?> clazz = null;

        try {
            return super.findClass(name);
        } catch (ClassNotFoundException cnfe) {
            SocketChannel channel = null;
            try {
                long tStart = System.currentTimeMillis();
                // Get channel
                channel = getChannel();
                ByteBuffer answerBuffer = sendRequest(new ClassRequest(name), channel);

                // Gets opCode
                byte opCode = getOpCode(answerBuffer, channel);

                // stats
                timeToDownload = timeToDownload + (System.currentTimeMillis() - tStart);

                // Switch :
                switch (opCode) {
                case ProtocolConstants.CLASS_ANSWER:
                    ClassAnswer classAnswer = new ClassAnswer(answerBuffer);
                    try {
                        clazz = loadClass(name, classAnswer.getByteCode());
                    } catch (IOException e) {
                        throw new ClassNotFoundException("Cannot find the class", e);
                    }
                    nbClasses++;
                    nbBytes = nbBytes + classAnswer.getByteCode().length;
                    // display statistics (use sysout)
                    if (Boolean.getBoolean("smart.debug.verbose")) {
                        System.out.println("Downloaded class '" + name + "'.");
                    }
                    break;
                case ProtocolConstants.CLASS_NOT_FOUND:
                    ClassNotFound classNotFound = new ClassNotFound(answerBuffer);
                    throw new ClassNotFoundException("The class '" + classNotFound.getName()
                            + "' was not found on the remote side");
                default:
                    throw new ClassNotFoundException("Invalid opCode '" + opCode + "' received");
                }
            } finally {
                // cleanup
                cleanChannel(channel);
            }
        }

        return clazz;

    }

    /**
     * Ask and return the remote PROVIDER_URL in order to connect with RMI.
     * @return a string with the PROVIDER_URL value.
     */
    public String getProviderURL() {
        String providerURL = null;
        SocketChannel channel = null;
        try {
            long tStart = System.currentTimeMillis();
            // Get channel
            channel = getChannel();
            ByteBuffer answerBuffer = sendRequest(new ProviderURLRequest(), channel);

            // Gets opCode
            byte opCode = getOpCode(answerBuffer, channel);

            // stats
            timeToDownload = timeToDownload + (System.currentTimeMillis() - tStart);

            // Switch :
            switch (opCode) {
            case ProtocolConstants.PROVIDER_URL_ANSWER:
                ProviderURLAnswer providerURLAnswer = new ProviderURLAnswer(answerBuffer);
                providerURL = providerURLAnswer.getProviderURL();
                break;
            default:
                throw new IllegalStateException("Invalid opCode '" + opCode + "' received");
            }
        } finally {
            // cleanup
            cleanChannel(channel);
        }
        return providerURL;
    }

    /**
     * Gets the operation code from the current buffer.
     * @param buffer the buffer to analyze.
     * @param channel the channel which is use for the exchange.
     * @return the operation code.
     */
    private byte getOpCode(final ByteBuffer buffer, final SocketChannel channel) {
        if (buffer == null) {
            throw new IllegalStateException("Empty buffer received");
        }

        // Check if it is a protocol that we manage
        byte version = buffer.get(0);
        if (version != PROTOCOL_VERSION) {
            cleanChannel(channel);
            throw new IllegalStateException("Invalid protocol version : waiting '" + PROTOCOL_VERSION + "', got '" + version
                    + "'.");
        }

        // Get operation asked by client
        byte opCode = buffer.get(1);
        // Length
        int length = buffer.getInt(2);
        if (length < 0) {
            cleanChannel(channel);
            throw new IllegalStateException("Invalid length for client '" + length + "'.");
        }
        return opCode;
    }

    /**
     * Finds the resource with the specified name on the URL search path. <br>
     * If resource is not found locally, search on the remote side.
     * @param name the name of the resource
     * @return a <code>URL</code> for the resource, or <code>null</code> if
     *         the resource could not be found.
     */
    @Override
    public synchronized URL findResource(final String name) {
        URL url = null;
        url = super.findResource(name);

        if (url != null) {
            return url;
        }

        if (name.startsWith("META-INF")) {
            return null;
        }

        SocketChannel channel = null;
        try {
            long tStart = System.currentTimeMillis();

            // Get channel
            channel = getChannel();
            ByteBuffer answerBuffer = sendRequest(new ResourceRequest(name), channel);

            // Gets opCode
            byte opCode = getOpCode(answerBuffer, channel);

            // stats
            timeToDownload = timeToDownload + (System.currentTimeMillis() - tStart);

            // Switch :
            switch (opCode) {
            case ProtocolConstants.RESOURCE_ANSWER:
                ResourceAnswer resourceAnswer = new ResourceAnswer(answerBuffer);
                String resourceName = resourceAnswer.getResourceName();
                byte[] bytes = resourceAnswer.getBytes();

                nbResources++;
                nbBytes = nbBytes + resourceAnswer.getBytes().length;

                // convert / into File.separator
                String[] tokens = resourceName.split("/");
                StringBuilder sb = new StringBuilder();
                for (String token : tokens) {
                    if (sb.length() > 0) {
                        sb.append(File.separator);
                    }
                    sb.append(token);
                }

                // Create parent dir if does not exist
                File urlFile = new File(this.tmpDirectory, sb.toString());
                if (!urlFile.getParentFile().exists()) {
                    urlFile.getParentFile().mkdir();
                }

                // dump stream
                FileOutputStream fos = new FileOutputStream(urlFile);
                fos.write(bytes);
                fos.close();
                url = urlFile.toURI().toURL();
                break;
            case ProtocolConstants.RESOURCE_NOT_FOUND:
                url = null;
                break;
            default:
                throw new IllegalStateException("Invalid opCode '" + opCode + "' received");
            }
        } catch (Throwable e) {
            logger.log(Level.SEVERE, "Cannot handle : findResource '" + name + "'", e);
        } finally {
            // cleanup
            cleanChannel(channel);
        }

        return url;
    }

    /**
     * Defines a class by loading the bytecode for the given class name.
     * @param className the name of the class to define
     * @param bytecode the bytecode of the class
     * @return the class that was defined
     * @throws IOException if the class cannot be defined.
     */

    private Class<?> loadClass(final String className, final byte[] bytecode) throws IOException {

        // override classDefine (as it is protected) and define the class.
        Class<?> clazz = null;
        try {
            ClassLoader loader = this;
            java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class,
                    int.class, int.class);

            // protected method invocaton
            method.setAccessible(true);
            try {
                clazz = (Class<?>) method.invoke(loader, className, bytecode, Integer.valueOf(0), Integer
                        .valueOf(bytecode.length));
            } finally {
                method.setAccessible(false);
            }
        } catch (Exception e) {
            IOException ioe = new IOException("Cannot define class with name '" + className + "'.");
            ioe.initCause(e);
            throw ioe;
        }
        return clazz;
    }

    /**
     * Remove any extra character from a hostname.
     * @param host the given string to cleanup
     * @return the string that has been cleanup
     */
    protected String cleanup(final String host) {
        return host.replace(":", "-").replace(".", "-");
    }


    /**
     * Hook that is called when process is going to shutdown.
     * @author Florent Benoit
     */
    static class ShutdownHook extends Thread {

        /**
         * Constructor for the given directory.
         * @param tmpDirectory the directory to remove at the end
         */
        public ShutdownHook(final File tmpDirectory) {
            this.tmpDirectory = tmpDirectory;
        }

        /**
         * Directory used to store temporary resources that are downloaded.
         */
        private File tmpDirectory = null;

        /**
         * Display stats.
         */
        @Override
        public void run() {
           delete(tmpDirectory);
            // display statistics (use sysout)
            if (Boolean.getBoolean("smart.debug")) {
                System.out.println("Downloaded '" + nbClasses + "' classes, '" + nbResources + "' resources for a total of '"
                        + nbBytes + "' bytes and it took '" + timeToDownload + "' ms.");
            }

        }

        /**
          * Delete the given directory.
          * @param path the path to remove
          */
        private static boolean delete(final File path) {
            if (path.exists() ) {
                File[] children = path.listFiles();
                if (children != null) {
                    for (File child : children) {
                        if (child.isDirectory()) {
                            delete(child);
                        } else {
                            child.delete();
                        }
                    }
                }
            }
            return path.delete();
        }
    }
}
TOP

Related Classes of org.ow2.easybeans.component.smartclient.client.AskingClassLoader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.