12

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).

Tupac
  • 647
  • 10
  • 34
pafau k.
  • 1,659
  • 12
  • 20
  • 1
    Do you truly mean ["memory mapped"](http://en.wikipedia.org/wiki/Memory-mapped_file), or do you mean "an object which only exists in-memory?" – Matt Ball Jun 29 '13 at 21:26
  • i also wonder if it's possible, as memory is considered private per process... – android developer Jun 29 '13 at 21:37
  • What's the code you're using? And why does it have to be URL? If you'll show the code we might come up with a solution that well accepts some generic `Resource` or the such. – yair Jun 29 '13 at 21:54
  • @MattBall sorry, I meant 'an object which only exists in-memory'. I just don't want to write to disk, if possible. – pafau k. Jun 29 '13 at 23:07
  • @yair I doesn't have to be an URL. I'm looking for a way to feed a class which scans the classpath with a file - without actually writing the file to disk. – pafau k. Jun 29 '13 at 23:07

2 Answers2

15

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());
                }

            };
        }

    }
}
NSV
  • 518
  • 7
  • 12
  • `byte[] data = IOUtils.toByteArray(url.openConnection().getInputStream());` How is IOUtils being obtained? Eclipse IDE Compiler says: **The type sun.security.util.IOUtils is not accessible** – Sascha B. Jul 11 '20 at 20:33
  • 2
    It is part of apache commons io: https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/IOUtils.html#toByteArray-java.io.InputStream- – NSV Jul 13 '20 at 08:17
  • Thanks for this, been looking for something similar for storing LESS files converted to standard CSS files and then loading them based on a URL. Also, if you don't want to add the Apache Commons dependency, then use `url.openConnection().getInputStream().readAllBytes()` available since Java 9 – john16384 Jan 29 '21 at 12:23
  • Thanks for this ready-to-use working example, including the WeakHashmap preventing memory leaks... – metatechbe Jan 25 '22 at 09:46
0

We can use the Jimfs google library for that.

First, we need to add the maven dependency to our project:

<dependency>
  <groupId>com.google.jimfs</groupId>
  <artifactId>jimfs</artifactId>
  <version>1.2</version>
</dependency>

After that, we need to configure our filesystem behavior, and write our String content to the in-memory file, like this:

public static final String INPUT =
      "\n"
          + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
          + "<note>\n"
          + "  <to>Tove</to>\n"
          + "  <from>Jani</from>\n"
          + "  <heading>Reminder</heading>\n"
          + "  <body>Don't forget me this weekend!</body>\n"
          + "</note>";

@Test
void usingJIMFS() throws IOException {
  try (var fs = Jimfs.newFileSystem(Configuration.unix())) {
    var path = fs.getPath(UUID.randomUUID().toString());
    Files.writeString(path, INPUT);
    var url = path.toUri().toURL();

    assertThat(url.getProtocol()).isEqualTo("jimfs");
    assertThat(Resources.asCharSource(url, UTF_8).read()).isEqualTo(INPUT);
  }
}

We can find more examples in the official repository.

If we look inside the jimfs source code we will find the implementation is similar to @NSV answer.

JuanMoreno
  • 1,991
  • 1
  • 20
  • 30