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
  • 12
  • 37
pafau k.
  • 1,667
  • 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
  • 13
  • `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
  • Caveat : using the WeakHashMap, a hard-reference to the URL object being returned must be kept by the caller until the processing is finished. Otherwise, the entry might be removed too soon from the map by the garbage collector (G1 with small heap in my case), depending on how soon it is triggered. I figured this out the hard way... – metatechbe Sep 22 '22 at 12:25
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
  • 2,498
  • 1
  • 25
  • 34