/* * The contents of this file are subject to the Open Software License * Version 3.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.opensource.org/licenses/osl-3.0.txt * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. */ package org.mulgara.server.rmi; import java.util.Hashtable; import java.net.URL; import java.net.URLConnection; import java.io.IOException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.rmi.UnmarshalException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NameClassPair; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import org.mulgara.util.ClassDescriber; import org.mulgara.util.ClassDescriberXML; /** * This class represents a connection to an RMI server. Reading from this connection * results in an Object coming across the connection, or an XML string to describe * what could have come over if the RMI deserializer cannot find a local class to * instantiate the object. * * @created Mar 28, 2008 * @author Paula Gearon * @copyright © 2007 <a href="mailto:pgearon@users.sourceforge.net">Paula Gearon</a> * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a> */ public class RmiURLConnection extends URLConnection { /** The context of the registry containing the RMI references. */ private Context rmiRegistryContext = null; /** A string description of the object referenced by the URL. */ private String description = null; /** An object representing what the URL refers to. If possible, this is a remote object, else a String. */ private Object content = null; /** Contains the raw bytes representing the data that will be "read" from this connection. */ private byte[] buffer = null; /** * Build a URLConnection to an RMI server. * @param url The URL to use for locating the service on the server. */ protected RmiURLConnection(URL url) { super(url); setDefaultUseCaches(false); setDefaultAllowUserInteraction(false); } /** * This type of connection does not permit user interaction. * @see java.net.URLConnection#setAllowUserInteraction(boolean) */ public void setAllowUserInteraction(boolean allow) { if (allow) throw new IllegalStateException("Not valid to interact with an rmi connection"); } /** * Caching is not valid for this kind of connection. * @see java.net.URLConnection#setUseCaches(boolean) */ public void setUseCaches(boolean use) { if (use) throw new IllegalStateException("Not valid to cache an rmi connection"); } /** * Connects to the specified service, and gets what content it can. * @see java.net.URLConnection#connect() */ public void connect() throws IOException { Hashtable<String,String> environment = new Hashtable<String,String>(); environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory"); environment.put(Context.PROVIDER_URL, url.getProtocol() + "://" + url.getAuthority()); String name = url.getPath().substring(1); try { rmiRegistryContext = new InitialContext(environment); updateContent(name); } catch (NamingException ne) { throw new IOException("Unable to establish connection", ne); } } /** * Reads the number of bytes for the content that can be read from this connection. * @return The number of bytes that can be returned from {@link #getContent()}. * @see java.net.URLConnection#getContentLength() */ public int getContentLength() { return (buffer == null) ? 0 : buffer.length; } /** * Gets the data representing the service connected to. * @return Either a remote object, accessed through RMI, or a String containing XML if * that object cannot be instantiated. * @throws IOException If not connected. * @see java.net.URLConnection#getContent() */ public Object getContent() throws IOException { if (content == null) throw new IOException("Not connected"); return content; } /** * Gets the data representing the service connected to. Using a list of desired classes * to specify the desired return type. The class array will be searched for a match with * the following, with the first match being the returned type: * <ol><li>The class of the Remote object obtained through RMI.</li> * <li>{@link java.lang.String}: the result will contain an XML description of the object.</li> * <li>An array of <code>byte</code>: the result will contain a serialization of either * the remote object or the XML description if the remote object cannot be instantiated.</li></ol> * Otherwise, a <code>null</code> is returned. * @return Either a remote object, accessed through RMI, or a String containing XML if * that object cannot be instantiated. * @throws IOException If not connected. * @see java.net.URLConnection#getContent() */ @SuppressWarnings("rawtypes") public Object getContent(Class[] cls) throws IOException { if (content == null) throw new IOException("Not connected"); if (testType(content.getClass(), cls)) return content; if (testType(String.class, cls)) return description; if (testType(byte[].class, cls)) return buffer; return null; } /** * Gets an input stream to read bytes from this connection. * @return an input stream that will return the data of the content. * @throws IOException If not connected. * @see java.net.URLConnection#getInputStream() */ public InputStream getInputStream() throws IOException { if (buffer == null) throw new IOException("Not connected"); return new ByteArrayInputStream(buffer); } /** * Utility to check if a class or one of its supertypes is present in an array. * @param clazz The class to check for. * @param cls The array of classes to test for the presence of clazz or one of its supertypes. * @return <code>true</code> if the class or one of its supertypes is present in cls. */ private static boolean testType(Class<?> clazz, Class<?>[] cls) { for (Class<?> c: cls) if (c.isAssignableFrom(clazz)) return true; return false; } /** * Gets data from a server by name, and creates content at this side to represent it. * Binary content will be created if possible, but a string containing an XML representation * will always be created. * @param name The name of the RMI service to get from the server. * @throws NamingException Looking up the name on the server failed. * @throws IOException Transfering data from the server failed. */ private void updateContent(String name) throws NamingException, IOException { if (name.length() == 0) { // server only description = getContextDescription(false); content = description; buffer = description.getBytes(); } else { // service name. Get the object. try { Object content = rmiRegistryContext.lookup(name); description = getServiceDescription(content); buffer = (content instanceof Serializable) ? serialize((Serializable)content) : description.getBytes(); } catch (NamingException ne) { Throwable e = ne.getCause(); if (!(e instanceof UnmarshalException)) throw ne; if (e == null || !(e.getCause() instanceof ClassNotFoundException)) throw ne; description = getContextDescription(true); content = description; buffer = description.getBytes(); } } } /** * Create an XML description of a {@link javax.naming.Context} from the server. * @param outOfScope <code>true</code> if the class for this context is not in the classpath. * @return An XML string describing the current RMI context. * @throws NamingException If the RMI context could not be accessed. */ private String getContextDescription(boolean outOfScope) throws NamingException { StringBuilder dsc = getHeader(); dsc.append("<context name=\"").append(rmiRegistryContext.getNameInNamespace()).append("\">\n"); NamingEnumeration<NameClassPair> ne = rmiRegistryContext.list(""); while (ne.hasMore()) { NameClassPair nc = ne.next(); dsc.append(" <name value=\"").append(nc.getName()); if (nc.isRelative()) dsc.append("\" relative=\"").append(nc.isRelative()); dsc.append("\">\n"); dsc.append(" <class name=\"").append(nc.getClassName()).append("\""); if (outOfScope) dsc.append(" inscope=\"false\""); dsc.append("/>\n </name>\n"); } dsc.append("</context>"); return dsc.toString(); } /** * Create an XML description of an RMI service. * @param obj The Remote RMI object representing the service. * @return An XML string describing the service class. * @throws NamingException If the RMI data could not be read. */ private String getServiceDescription(Object obj) throws NamingException { ClassDescriber cd = new ClassDescriberXML(obj); StringBuilder b = getHeader().append("<service>\n"); b.append(cd.getDescription(1)); b.append("</service>"); return b.toString(); } /** * Creates a {@link java.lang.StringBuilder} containing an XML header. This is ready to have * more data appended to it. * @return A new StringBuilder with the header in it. */ private static StringBuilder getHeader() { StringBuilder header = new StringBuilder("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n\n"); return header; } /** * Convert an object into an array of bytes. * @param obj The object to serialize. * @return a <code>byte[]</code> containing the serialization of obj. * @throws IOException If there was an internal serialization error. */ private byte[] serialize(Serializable obj) throws IOException { ByteArrayOutputStream dataOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(dataOut); out.writeObject(obj); return dataOut.toByteArray(); } }