Search code examples
javajpaclassloader

Is it possible to create an URL pointing to an in-memory object?


I'm trying to extend my library for integrating Swing and JPA by making JPA config as automatic (and portable) as can be done, and it means programmatically adding <class> elements. (I know it can be done via Hibernate's AnnotationConfiguration or EclipseLInk's ServerSession, but - portability). I'd also like to avoid using Spring just for this single purpose.

I can create a persistence.xml on the fly, and fill it with <class> elements from specified packages (via the Reflections library). The problem starts when I try to feed this persistence.xml to a JPA provider. The only way I can think of is setting up a URLClassLoader, but I can't think of a way what wouldn't make me write the file to the disk somewhere first, for sole ability to obtain a valid URL. Setting up a socket for serving the file via an URL(localhost:xxxx) seems... I don't know, evil?

Does anyone have an idea how I could solve this problem? I know it sounds like a lot of work to avoid using one library, but I'd just like to know if it can be done.

EDIT (a try at being more clear):

Dynamically generated XML is kept in a String object. I don't know how to make it available to a persistence provider. Also, I want to avoid writing the file to disk.

For purpose of my problem, a persistence provider is just a class which scans the classpath for META-INF/persistence.xml. Some implementations can be made to accept dynamic creation of XML, but there is no common interface (especially for a crucial part of the file, the <class> tags).

My idea is to set up a custom ClassLoader - if you have any other I'd be grateful, I'm not set on this one.

The only easily extendable/configurable one I could find was a URLClassLoader. It works on URL objects, and I don't know if I can create one without actually writing XML to disk first.

That's how I'm setting things up, but it's working by writing the persistenceXmlFile = new File("META-INF/persistence.xml") to disk:

Thread.currentThread().setContextClassLoader(
    new URLResourceClassLoader(
        new URL[] { persistenceXmlFile.toURI().toURL() },
        Thread.currentThread().getContextClassLoader()
    )
);

URLResourceClassLoader is URLCLassLoader's subclass, which allows for looking up resources as well as classes, by overriding public Enumeration<URL> findResources(String name).


Solution

  • Maybe a bit late (after 4 years), but for others that are looking for a similar solution, you may be able to use the URL factory I created:

    public class InMemoryURLFactory {
    
        public static void main(String... args) throws Exception {
            URL url = InMemoryURLFactory.getInstance().build("/this/is/a/test.txt", "This is a test!");
            byte[] data = IOUtils.toByteArray(url.openConnection().getInputStream());
            // Prints out: This is a test!
            System.out.println(new String(data));
        }
    
        private final Map<URL, byte[]> contents = new WeakHashMap<>();
        private final URLStreamHandler handler = new InMemoryStreamHandler();
    
        private static InMemoryURLFactory instance = null;
    
        public static synchronized InMemoryURLFactory getInstance() {
            if(instance == null)
                instance = new InMemoryURLFactory();
            return instance;
        }
    
        private InMemoryURLFactory() {
    
        }
    
        public URL build(String path, String data) {
            try {
                return build(path, data.getBytes("UTF-8"));
            } catch (UnsupportedEncodingException ex) {
                throw new RuntimeException(ex);
            }
        }
    
        public URL build(String path, byte[] data) {
            try {
                URL url = new URL("memory", "", -1, path, handler);
                contents.put(url, data);
                return url;
            } catch (MalformedURLException ex) {
                throw new RuntimeException(ex);
            }
        }
    
        private class InMemoryStreamHandler extends URLStreamHandler {
    
            @Override
            protected URLConnection openConnection(URL u) throws IOException {
                if(!u.getProtocol().equals("memory")) {
                    throw new IOException("Cannot handle protocol: " + u.getProtocol());
                }
                return new URLConnection(u) {
    
                    private byte[] data = null;
    
                    @Override
                    public void connect() throws IOException {
                        initDataIfNeeded();
                        checkDataAvailability();
                        // Protected field from superclass
                        connected = true;
                    }
    
                    @Override
                    public long getContentLengthLong() {
                        initDataIfNeeded();
                        if(data == null)
                            return 0;
                        return data.length;
                    }
    
                    @Override
                    public InputStream getInputStream() throws IOException {
                        initDataIfNeeded();
                        checkDataAvailability();
                        return new ByteArrayInputStream(data);
                    }
    
                    private void initDataIfNeeded() {
                        if(data == null)
                            data = contents.get(u);
                    }
    
                    private void checkDataAvailability() throws IOException {
                        if(data == null)
                            throw new IOException("In-memory data cannot be found for: " + u.getPath());
                    }
    
                };
            }
    
        }
    }