Git Product home page Git Product logo

Comments (26)

steinarb avatar steinarb commented on August 21, 2024 2

Hi Harald,

Great! I will have a PR ready as soon as I have at least smoketested it on JFIF files.

The changes are on the branch linked to above, and are as I described in the first post (a config in in the parent, and referencing the maven-bundle-plugin in of each pom building a jar file), if you wish to inspect them.

I'm trying to leave as much to maven-bundle-plugin defaults as possible.

Re: package conflicts: Noted! I will notify you about any such conflicts I see.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024 1

I made a fork of TwelweMonkeys and made a push of a fix to a branch.

But I haven't made a PR yet.

I will see if I can make it work first.

It may be that I have to adjust some imports and exports manually, and there may be issues with multiple jars/bundles exporting the same package. I haven't checked yet. If it is multiple jars/bundles exporting the same package, then fatjar'ing is the only way to go (other than moving packages).

from twelvemonkeys.

zspitzer avatar zspitzer commented on August 21, 2024 1

We use Twelve monkeys in Lucee, it required a bit of plumbing

https://luceeserver.atlassian.net/browse/LDEV-3961

lucee/Lucee@228bbea

and re-wrapping the all bundles

https://github.com/orgs/lucee/repositories?language=&q=twelvemonkeys&sort=&type=all

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024 1

Status update:

  1. Just adding the OSGi headers for export and import of packages wasn't enough. I also need to add Require-Capability and Provide-Capability MANIFEST.MF headers to all jar files implementing IMageIO SPI plugins
  2. Luckily I think it will be possible to add this in the of imageio/pom.xml (right now I just have it for testing in the imageio-jpeg jar), which should minimize the config clutter, because I think it will be the same headers for all ImageIO plugins
  3. I've run into a problem during testing because Apache Aries SPI Fly, which implements the mediation between SPI and OSGi for some reason has decided that one of my beans and the JDBC ResultSet are supposed to be unified and fails because they don't have a common superclass. I think this is a bug in SPI Fly, and I will be debugging to see what in my code tickles the bug and what I can do to work around it. https://issues.apache.org/jira/browse/ARIES-2110 (but it has slowed down me testing what I was supposed to test, which is the Provide-Capability headers for the twelvemonkeys imageio plugins)
  4. I ran into another weird thing today, which was that what worked yesterday did not work today. I figured out what it was: maven downloads new twelvemonkeys snapshots from wherever they are deployed. So to get things working with OSGi I have to make sure that I have a local snapshot build that is newer than the downloaded one. Not a big problem but it had me scratching my head for a while until I decided to look at the MANIFEST.MF of the snapshot in ~/.m2/repository/

So there is progress, but slow progress.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024 1

I had a lightning speach on JavaZone, but now that is over, I have started looking into this again.

Current status:

  1. I have created a fork of TwelveMonkeys and created a branch with my changes
  2. I have made a test-application with a rudimentary react GUI that uploads the URL of a JPEG file and then returns metadata for the image (the comment, if any, is extracted using imageio). The master branch currently uses the built-in imageio
  3. Instructions for how to build and run the test application can be found in the README
  4. I have created a branch on the test-application where I am trying to use Apache Aries SPI-fly to inject TwelveMonkeys into the imageio inside OSGi (so far without luck, still using com.sun.imageio.plugins.jpeg.JPEGImageReader according to the remote debug)

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Note: If there is a change of the PR being accepted I can create a PR for it.

I'm talking about the minimal approach here: just making the jar files OSGi bundles

from twelvemonkeys.

haraldk avatar haraldk commented on August 21, 2024

Hi Steinar,

Thanks for looking into this!

I don't use OSGi for anything, and won't be able to verify it's correctness, but as long as we don't destroy the normal SPI registration (which is, for better or worse, the way ImageIO plugins are registered) and other functionality of the plugins, I think I can accept a PR for this.

The centralized registry/SPI approach isn't great for normal WAR/EAR container deployment either, but at least it can be worked around using the context listener approach. I wish there was a better way...

I don't think there should be multiple JARs containing the same package (with possible exception of the test-JARs), as that causes issues with normal Java modules as well. If you find any issues related to this, let me know, and I'll help restructure the code if needed.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

(it's the rewrapping I'm hoping to avoid by making the jars be bundles in the first place).

If you know OSGi stuff I'll appreciate you taking a look at the branch I've made.

It's pretty simple: a low energy approach using defaults as much as possible (it's my standard maven-bundle-plugin config lifted over).

from twelvemonkeys.

zspitzer avatar zspitzer commented on August 21, 2024

I know enough about OSGI to understand it, but that's about it

um, which branch do you mean? I see a lot of links but none seem to be a branch?

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

I thought the branch showed up here, but it was just the single commit: steinarb@daa9ef8 (it is just a single commit)

from twelvemonkeys.

haraldk avatar haraldk commented on August 21, 2024

I had a lightning speach on JavaZone

I was there, but missed it, sorry... 🀷🏻
But now I know slightly more about IPv6 than I did before... πŸ˜‰

I just skimmed the SPI-Fly docs, and it seems it should work for this use case.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Yes, SPI-Fly should work. I think it is just an issue of finding the right services to provide and require.

Now that I have the minimal test application this should be easier to do.

(Saw your co-presented talk at JavaZone a while back (was it before the pandemic?), that's how I knew about TwelveMonkeys)

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Now I'm a bit further:

  1. I switched spifly logging in apache karaf from the default WARN to INFO
  2. I published javax.imageio.spi.ImageReaderSpi from the MANIFEST.MF of imageio-jpeg
    Provide-Capability: osgi.serviceloader;osgi.serviceloader="javax.imageio
     .spi.ImageReaderSpi"
    
  3. I imported all javax.imageio.spi.ImageReaderSpi services in the MANIFEST.MF of twelvemonkeys-karaf-demo
    Require-Capability: osgi.extender;filter:="(osgi.extender=osgi.servicelo
     ader.processor)",osgi.service;filter:="(objectClass=no.priv.bang.demos.
     frontendkarafdemo.ImageService)";effective:=active,osgi.service;filter:
     ="(objectClass=org.osgi.service.log.LogService)";effective:=active,osgi
     .serviceloader;filter:="(osgi.serviceloader=javax.imageio.spi.ImageRead
     erSpi)";cardinality:=multiple,osgi.extender;filter:="(&(osgi.extender=o
     sgi.component)(version>=1.5.0)(!(version>=2.0.0)))",osgi.implementation
     ;filter:="(&(osgi.implementation=osgi.http)(version>=1.1.0)(!(version>=
     2.0.0)))",osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=17))"
    
  4. According to the karaf.log, the twelvemonkeys imageio-jpeg was found and registered with imageio
    2023-09-16T16:29:42,032 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi of service javax.imageio.spi.ImageReaderSpi in bundle com.twelvemonkeys.imageio.jpeg
    2023-09-16T16:29:42,033 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   no.priv.bang.servlet.frontend/1.6.8
    2023-09-16T16:29:42,035 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.twelvemonkeys.imageio.tiff/3.10.0.SNAPSHOT
    
  5. Nevertheless, the com.sun.imageio.plugins.jpeg.JPEGImageReader was used (in this code)
    2023-09-16T16:35:20,357 | INFO  | qtp2023681064-374 | ImageServiceProvider             | 82 - no.priv.bang.demos.twelvemonkeys-karaf-demo - 2.0.0.SNAPSHOT | reader class: com.sun.imageio.plugins.jpeg.JPEGImageReader
    

Tips to where to go from here is appreciated.

Do I need to export all services for one of them to work?

Do I need to export both readers and writers?

Do I need some more dependencies?

Have I picked the wrong JPEG plugin to export a service from? (I planned to just start with one and see what I got, but I may have picked the wrong one? Would be nice to get feedback from someone that knows imageio and twelvemonkeys)

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Now I've done osgi.serviceloader exports for all OSGi bundles in the imageio directory (I've exported the services found in the META-INF/services directories).

According to the logs spifly picks up and includes services.

But my example code still gets com.sun.imageio.plugins.jpeg.JPEGImageReader instead of com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi

Is there something wrong/missing in the way my example code accesses imageio?

Have I forgotten any magic invocations for spifly?

What I am doing is adding maven dependencies and add a Require-Capability for javax.imageio.spi.ImageReaderSpi (which is satisfied, because otherwise the app wouldn't have loaded).

Do I need to do something beyond this?

Here are the karaf.log messages from the spifly registration of the SPI services:

2023-09-17T19:30:20,712 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.twelvemonkeys.imageio.core/3.10.0.SNAPSHOT
2023-09-17T19:30:20,716 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi of service javax.imageio.spi.ImageInputStreamSpi in bundle com.twelvemonkeys.imageio.core
2023-09-17T19:30:20,718 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi of service javax.imageio.spi.ImageInputStreamSpi in bundle com.twelvemonkeys.imageio.core
2023-09-17T19:30:20,720 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.stream.BufferedInputStreamImageInputStreamSpi of service javax.imageio.spi.ImageInputStreamSpi in bundle com.twelvemonkeys.imageio.core
2023-09-17T19:30:20,722 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi of service javax.imageio.spi.ImageInputStreamSpi in bundle com.twelvemonkeys.imageio.core
2023-09-17T19:30:20,723 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.twelvemonkeys.imageio.psd/3.10.0.SNAPSHOT
2023-09-17T19:30:20,726 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi of service javax.imageio.spi.ImageReaderSpi in bundle com.twelvemonkeys.imageio.psd
2023-09-17T19:30:20,727 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.twelvemonkeys.imageio.metadata/3.10.0.SNAPSHOT
2023-09-17T19:30:20,729 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.fasterxml.jackson.core.jackson-annotations/2.15.2
2023-09-17T19:30:20,731 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.fasterxml.jackson.core.jackson-core/2.15.2
2023-09-17T19:30:20,732 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.fasterxml.jackson.core.jackson-databind/2.15.2
2023-09-17T19:30:20,734 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.twelvemonkeys.imageio.tiff/3.10.0.SNAPSHOT
2023-09-17T19:30:20,737 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi of service javax.imageio.spi.ImageReaderSpi in bundle com.twelvemonkeys.imageio.tiff
2023-09-17T19:30:20,738 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi of service javax.imageio.spi.ImageReaderSpi in bundle com.twelvemonkeys.imageio.tiff
2023-09-17T19:30:20,739 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   org.apache.commons.lang3/3.12.0
2023-09-17T19:30:20,741 | INFO  | features-3-thread-1 | FeaturesServiceImpl              | 19 - org.apache.karaf.features.core - 4.4.3 |   com.twelvemonkeys.imageio.jpeg/3.10.0.SNAPSHOT
2023-09-17T19:30:20,744 | INFO  | features-3-thread-1 | BaseActivator                    | 87 - org.apache.aries.spifly.dynamic.bundle - 1.3.4 | Registered provider com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi of service javax.imageio.spi.ImageReaderSpi in bundle com.twelvemonkeys.imageio.jpeg

from twelvemonkeys.

haraldk avatar haraldk commented on August 21, 2024

Your use of ImageIO seems fine. It's strongly recommended to dispose() of the ImageReader in a finally block to not leak resources, but that won't affect OSGi lookup/registering.

I'm not exactly sure what Registered provider com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReaderSpi of service javax.imageio.spi.ImageReaderSpi in bundle com.twelvemonkeys.imageio.jpeg means in this context. Based on the symptoms, it doesn't seem to be that the SPI is registered in the IIOServiceRegistry. Are you able to attach a debugger while running this code? If so, can you put a break point in the JPEGImageReaderSpi.onRegistration method and see if it's hit at all? And if it is, is the delegate provider found and the ordering set correctly?

Another thing to check, if debugging isn't possible, is to loop through your readers iterator in the above linked code, to print the names of all possible JPEG capable ImageReaders. If you only have the built-in, then the problem is still that the SPIs are not correctly registered. If the TwelveMonkeys one is there, but ordered after the built-in, then there's an issue with the ordering I would need to look further into.

Have you tried another format like PSD, where there is no built-in plugin? Does that work?

It's a bit hard to remote-debug based on log files, especially as I'm no OSGi guy... Maybe we should just meet up and fix this some day, if you are in Oslo? πŸ˜€

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Ok, I didn't have debugging into the java runtime library initially so I tried the second approach first: iterate over all readers. I basically just made the first "if (readers.hasNext)" into a while.

And if the logs are to be trusted, there is only one reader in readers (second iteration would probably crash the way I made it, attempting to consume the inputStream a second time, but that didn't happen either, and debugging the while loop also told me a single iteration through readers).

I had already installed openjdk-17-dbg without getting debug. Now I installed openjdk-17-source and that immediately gave me debugging into the java runtime library.

So I put a breakpoint in JPEGImageReaderSpi.onRegistration and that was never trigged.

So the conclusion from both debug approaches, is that the SPIs are not correctly registered.

It was good to get that clarified because then I won't have to go down many wrong paths or assumptions. πŸ˜„

My plan for now is to try to read up more on spifly to see if there is something I've forgotten to do. I'll look into work someone did earlier in a rebundled Twelvemonkeys (they got it working, but I think maybe they fatjar'd it...?).

Yes, I'm in Oslo so it would be possible to meet up and debug. Not sure where or when, and probably not during this week or the weekend.

from twelvemonkeys.

haraldk avatar haraldk commented on August 21, 2024

Thanks, that gives a slightly clearer view of the issue. πŸ‘πŸ»

You could of course try to invoke

    // Registers all locally available IIO plugins.
    ImageIO.scanForPlugins();

once, after the SpiFly registration is done, if you have something you can hook onto, as it doesn't seem that SpiFly is registering the Spis with the IIORegistry (not sure if this is intentional or a misconfiguration though). And see if that helps get the Spis registered. This is not something I generally recommend (as you might have issues when/after unloading your OSGi bundles later), but it might help us pinpoint the problem.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Since I don't have a component expecting injection of a service from the TwelveMonkeys bundles I don't have anything that I can expect to be run after the registration (the @activate method of a component will be run after all injected services are in place).

But what I can do, in my test application, is to create a rest endpoint that does the scan and create a"scan for imageio plugins" button in the GUI to do the scan (it is a test application after all).

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

I have added a REST endpoint to do ImageIO.scanForPlugins() and a react button to trigger the REST endpoint, to a branch in the test program https://github.com/steinarb/twelvemonkeys-karaf-demo/tree/use-twelvemonkeys-imageio

But both the rescan function and the actual metadata query only finds com.sun.imageio.plugins.jpeg.JPEGImageReader (and I'm not surprised when looking at the code of ImageIO.scanForPlugins(), because scanning a classpath that way doesn't work well in OSGi).

(Note that the unit test returns both JPEG readers, but that doesn't help us any)

But I'm not giving up yet. I will study all spifly docs and examples I can find.

I found this yesterday: https://github.com/Maurice-Betzel/twelvemonkeys-osgi

It uses the rebundle-into-fatjar approach, but I don't know if that's important (but it would actually make ImageIO.scanForPlugins() work, I think...?).

I added the SPI-Provider and SPI-Consumer MANIFEST.MF headers, but they didn't make any difference that I could see.

SPI-Provider and SPI-Consumer are Spifly-specific.

Ah! Interesting bit here: "Additionally services found in META-INF/services are registered in the OSGi Service Registry."

That means it should be possible to inject all of the SPI services into an OSGi component and register them manually (if everything else fails).

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Success! I added an OSGi DS component, that listens for the OSGi services created from SPI Services by Apache Aries Spifly, and registers the services with the imageio service registry:`

@Component
public class ImageioSpiRegistration {

    @Reference(cardinality = ReferenceCardinality.MULTIPLE)
    public void registerImageReaderSpi(ImageReaderSpi readerProvider) {
        IIORegistry.getDefaultInstance().registerServiceProvider(readerProvider);
    }

    @Reference(cardinality = ReferenceCardinality.MULTIPLE)
    public void registerImageWriterSpi(ImageWriterSpi writerProvider) {
        IIORegistry.getDefaultInstance().registerServiceProvider(writerProvider);
    }

    @Reference(cardinality = ReferenceCardinality.MULTIPLE)
    public void registerImageInputStreamSpi(ImageInputStream inputStreamProvider) {
        IIORegistry.getDefaultInstance().registerServiceProvider(inputStreamProvider);
    }

}

Adding this component to the OSGi runtime, makes imageio, called from a different OSGi component, pick com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader to read a JPEG (JFIF) file.

I tried using the Spifly-specific SPI-Consumer MANIFEST.MF header to call IIORegistry.getDefaultInstance().registerServiceProvider() but that didn't work.

I used the MANIFEST.MF header:

SPI-Consumer: javax.imageio.spi.IIORegistry#getDefaultInstance#registerServiceProvider

but I don't think Spifly is able to handle two chained method calls.

I think that if there had been a static registration method using this approach would have worked.

But using a dedicated OSGi component will work on other SPI service loaders than Aries spifly.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Added a PR for the change, with an explanation of the changes done to the pom files.

No changes to any code, only changes to pom files and only changes to the MANIFEST.MF of the files that will not affect non-OSGi applications.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

And if/when the above PR is accepted, if it is of interest I could create a different PR for an Twelvemonkeys/osgi-registrar module that will contain the above DS component that registers the Twelvemonkeys services with imageio and a README.md explaining how to use it from apache karaf.

from twelvemonkeys.

ben-manes avatar ben-manes commented on August 21, 2024

I don't use OSGi for anything, and won't be able to verify it's correctness

I don’t either and know very little, but was comforted by using pax-exam tests as a sanity check in my oss project (example).

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

I don't use OSGi for anything, and won't be able to verify it's correctness

I don’t either and know very little, but was comforted by using pax-exam tests as a sanity check in my oss project (example).

Ah! Pax exam tests I can do (if of interest)! πŸ˜ƒ

I even provided a pax exam test to the PosgreSQL JDBC build after creating a karaf feature (and possibly also a DataSourceProvider...? I can't remember it's been a while).

But as to where the adapter I've made that takes Twelvemonkey SPI services turned into OSGi services by Apache Aries Spifly, and registers them with the ImageIO ServiceRegistry should live: there is no need for it to live in Twelvemonkeys.

I could just create a standalone project imageio-spi-registrar-osgi-adapter (or something) and release it to maven central.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

But just stating for the record, so that there is no confusion: neither a pax exam unit test and/or the OSGi component I used to register services with the ImageIO service registry, are a requirement to accept the current PR.

The current PR will not affect non-OSGi applications in any way. The changes in the MR just add headers to the MANIFEST.MF of the jars. Headers that are not used by non-OSGi applications.

The result of the current MR has been tested by me in that it loads the imageio OSGi bundles in apache karaf and an application using imageio to read JPEG/JFIF metadata uses the Twelvemonkeys JPEG image reader to read the image.

from twelvemonkeys.

steinarb avatar steinarb commented on August 21, 2024

Nice! Thanks!

from twelvemonkeys.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.