Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GEOT-6964] Java 17 compatibility #3612

Merged
merged 2 commits into from
Oct 16, 2021
Merged

[GEOT-6964] Java 17 compatibility #3612

merged 2 commits into from
Oct 16, 2021

Conversation

bjornharrtell
Copy link
Contributor

@bjornharrtell bjornharrtell commented Aug 26, 2021

GEOT-6964 Powered by Pull Request Badge

Checklist

For core and extension modules:

  • New unit tests have been added covering the changes.
  • Documentation has been updated (if change is visible to end users).
  • There is an issue in GeoTools Jira (except for changes not visible to end users).
  • Commit message(s) must be in the form [GEOT-XYZW] Title of the Jira ticket.
  • Bug fixes and small new features are presented as a single commit.
  • The commit targets a single objective (if multiple focuses cannot be avoided, each one is in its own commit, and has a separate ticket describing it).

@bjornharrtell bjornharrtell changed the title Java 17 compatibility [GEOT-6964] Java 17 compatibility Aug 26, 2021
@bjornharrtell
Copy link
Contributor Author

The two remaining test case failures are very strange. They involve the function NetCDFUtilities.getAxisFormat and in short this is what is going on:

Home grown code attempts to find the date pattern of Jan 1, 2000. The pattern is found as MMM dd,yy which doesn't really seem correct and then it uses that to parse into a Date using SimpleDateFormat and Locale.CANADA. This, unexpectedly to me, work in Java 8 and Java 11 but in Java 16+ (and possible before that) it throws ParseException and message Unparseable date: "Jan 1, 2000". Fixing the pattern to "MMM dd, yyyy" does however not help. The only thing that does help is change it to not use Locale.CANADA. But I don't know what to make of this.. is it a JDK regression or it is just reliance of undefined behaviour? I don't really understand why it refuses to parse with Locale.CANADA anymore.

Change 30b8fa1 works for me locally but doesn't feel like a reliable fix.

@bjornharrtell
Copy link
Contributor Author

Not sure why I didn't see the test failure for module locally but now I do and the failure is:

2021-08-26T19:50:45.5294242Z [ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.546 s <<< FAILURE! - in org.geotools.appschema.jdbc.JoiningJDBCFeatureSourceTest
2021-08-26T19:50:45.5307768Z [ERROR] testMultipleIds(org.geotools.appschema.jdbc.JoiningJDBCFeatureSourceTest)  Time elapsed: 1.544 s  <<< ERROR!
2021-08-26T19:50:45.5312984Z org.mockito.exceptions.base.MockitoException: 
2021-08-26T19:50:45.5314596Z 
2021-08-26T19:50:45.5319808Z Mockito cannot mock this class: class org.geotools.jdbc.JDBCDataStore.
2021-08-26T19:50:45.5342773Z Can not mock final classes with the following settings :
2021-08-26T19:50:45.5425071Z  - explicit serialization (e.g. withSettings().serializable())
2021-08-26T19:50:45.5459297Z  - extra interfaces (e.g. withSettings().extraInterfaces(...))
2021-08-26T19:50:45.5459800Z 
2021-08-26T19:50:45.5463160Z You are seeing this disclaimer because Mockito is configured to create inlined mocks.
2021-08-26T19:50:45.5464368Z You can learn about inline mocks and their limitations under item #39 of the Mockito class javadoc.
2021-08-26T19:50:45.5466145Z 
2021-08-26T19:50:45.5470541Z Underlying exception : org.mockito.exceptions.base.MockitoException: Could not modify all classes [class org.geotools.jdbc.JDBCDataStore, class java.lang.Object, interface org.geotools.data.DataStore, interface org.geotools.data.GmlObjectStore, class org.geotools.data.store.ContentDataStore, interface org.geotools.data.DataAccess]
2021-08-26T19:50:45.5475781Z 	at org.geotools.appschema.jdbc.JoiningJDBCFeatureSourceTest.testMultipleIds(JoiningJDBCFeatureSourceTest.java:49)
2021-08-26T19:50:45.5480966Z Caused by: org.mockito.exceptions.base.MockitoException: Could not modify all classes [class org.geotools.jdbc.JDBCDataStore, class java.lang.Object, interface org.geotools.data.DataStore, interface org.geotools.data.GmlObjectStore, class org.geotools.data.store.ContentDataStore, interface org.geotools.data.DataAccess]
2021-08-26T19:50:45.5486415Z 	at org.geotools.appschema.jdbc.JoiningJDBCFeatureSourceTest.testMultipleIds(JoiningJDBCFeatureSourceTest.java:49)
2021-08-26T19:50:45.5488946Z Caused by: java.lang.IllegalStateException: 
2021-08-26T19:50:45.5489454Z 
2021-08-26T19:50:45.5490321Z Byte Buddy could not instrument all classes within the mock's type hierarchy
2021-08-26T19:50:45.5490771Z 
2021-08-26T19:50:45.5491653Z This problem should never occur for javac-compiled classes. This problem has been observed for classes that are:
2021-08-26T19:50:45.5492586Z  - Compiled by older versions of scalac
2021-08-26T19:50:45.5493307Z  - Classes that are part of the Android distribution
2021-08-26T19:50:45.5495282Z 	at org.geotools.appschema.jdbc.JoiningJDBCFeatureSourceTest.testMultipleIds(JoiningJDBCFeatureSourceTest.java:49)
2021-08-26T19:50:45.5497563Z Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 61
2021-08-26T19:50:45.5499832Z 	at org.geotools.appschema.jdbc.JoiningJDBCFeatureSourceTest.testMultipleIds(JoiningJDBCFeatureSourceTest.java:49)

Added commit to attempt upgrading Mockito.

@bjornharrtell
Copy link
Contributor Author

Upgrading Mockito seems effective but now a couple of test failures pop up in Cartographic CSS parser.. strange.

@bjornharrtell
Copy link
Contributor Author

Green Linux JDK 8, 11 and 17 GitHub CI 🎉

@dromagnoli
Copy link
Contributor

Hi @bjornharrtell .
in reference to the Date formatting topic with Locale.CANADA I honestly have no idea why it was using that locale (it's a piece of code from almost 10 years ago).
What happens if you switch it to English instead?

@aaime
Copy link
Member

aaime commented Aug 27, 2021

What happens if you switch it to English instead?

Yep, otherwise we'll get dates formatted in the default locale of the system...

@ianturton
Copy link
Member

Hi @bjornharrtell .
in reference to the Date formatting topic with Locale.CANADA I honestly have no idea why it was using that locale (it's a piece of code from almost 10 years ago).
What happens if you switch it to English instead?

The usual issue is with the time zone, never seen a Locale issue in our date handling code before. Does the locale affect the TZ at all?

@@ -1046,7 +1046,7 @@ public static Format getAxisFormat(final AxisType type, final String prototype)
// TODO: Improve me:
// Handle timeZone
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line is what would worry me!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure but that's been there forever

@bjornharrtell
Copy link
Contributor Author

Hi @bjornharrtell .
in reference to the Date formatting topic with Locale.CANADA I honestly have no idea why it was using that locale (it's a piece of code from almost 10 years ago).
What happens if you switch it to English instead?

Seems to be working with Locale.ENGLISH but I'm a bit scared over potential regressions with stuff we are not covering with tests.

@bjornharrtell
Copy link
Contributor Author

The Downstream integration build fails due to that GeoServer also have a test using reflection to override a private final static field. Pretty nasty in my opinion and should use the new public API I introduce here. But not sure how to make that happen unless merging this and adapt GeoServer ASAP.

@aaime
Copy link
Member

aaime commented Aug 28, 2021

@bjornharrtell just prepare twin PRs on the two projects, that build fine on your machine. Someone will confirm they do and then we can merge them together.

Btw, this is one example of things I was expecting to break solid in Java 17... and they don't?

@bjornharrtell
Copy link
Contributor Author

@bjornharrtell just prepare twin PRs on the two projects, that build fine on your machine. Someone will confirm they do and then we can merge them together.

Btw, this is one example of things I was expecting to break solid in Java 17... and they don't?

Agree, that is suspect.. could it be that we somehow do not cover it in tests?

@aaime
Copy link
Member

aaime commented Aug 30, 2021

@bjornharrtell maybe it's because we're telling the compiler to target Java 8?
Maybe try locally to up the version number there to 17 and see... that said, even if we decide to drop support for Java 8, the target would become java 11, not 17.... I've tried that, with the Java 17 RC release, and... nothing, it builds fine. Duh?? 🤣

@bjornharrtell
Copy link
Contributor Author

@aaime some more thought on this... it's reflection so it make sense that it doesn't cause compilation issues, however I would expect runtime issues covered by tests when run with the Java 17 JRE. Perhaps this will become more clear when I can invest som more time into the corresponding GeoServer issue with some more real life runtime experience.

@fgdrf
Copy link
Contributor

fgdrf commented Aug 30, 2021

Maybe I can add some details here...

At runtime you will get Warnings such as reported in GEOT-6967 (in this case with java 11 runtime started with https://github.com/locationtech/udig-platform) for ImageWorker. In our scenario we havent't seen any other issues but as @aaime stated: Only code thats loaded at runtime logs warnings. And we definitely do not use everything from GeoTools.

So to get feedback for the intergation tests, its required to compile and test with at least maven.target.level=11

@aaime
Copy link
Member

aaime commented Aug 30, 2021

Ah, found it, Maven was still using java 11 because I overrode the PATH, but not JAVA_HOME.
Here is the output of a build with java 17 and source/target set to 11:

[INFO] Running org.geotools.image.ImageWorkerTest
[ERROR] Tests run: 69, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 2.215 s <<< FAILURE! - in org.geotools.image.ImageWorkerTest
[ERROR] testJPEGWrite(org.geotools.image.ImageWorkerTest)  Time elapsed: 0.005 s  <<< ERROR!
java.lang.IllegalStateException: Class 'Unable to find JDK JPEG Writer' is illegal. It must be '{1}' or a derivated class.
	at org.geotools.image.ImageWorkerTest.testJPEGWrite(ImageWorkerTest.java:517)

[ERROR] testOpacityAlphaRGBDirect(org.geotools.image.ImageWorkerTest)  Time elapsed: 0.002 s  <<< ERROR!
java.lang.IllegalAccessError: class javax.media.jai.RasterAccessor (in unnamed module @0x2a48d10f) cannot access class sun.awt.image.BytePackedRaster (in module java.desktop) because module java.desktop does not export sun.awt.image to unnamed module @0x2a48d10f
	at org.geotools.image.ImageWorkerTest.testAlphaRGB(ImageWorkerTest.java:1256)
	at org

We need to get that writer spi instance, just allowing free SPI usage is to be avoided, ImageWorker.writeJPEG allows to choose which one to use at runtime (native vs JDK). We'll go there only if there is no other way, as that will break API and also the GeoServer image writing configurations.

However, it should be possible to get to the writer in other ways, for example, looping over the results of ImageIO.getImageWritersByMIMEType, checking the class names, and then grabbing the originating provider.

@bjornharrtell
Copy link
Contributor Author

@aaime ok still find it strange that it is not seen with Java 17 in JAVA_HOME but source/target left as is.

@bjornharrtell
Copy link
Contributor Author

@aaime revisiting this, I still don't understand why it is/would be needed to compile to target Java 11 to get the runtime problems. And in either case we want to target Java 8 to be able to do this in not too far future.

@aaime
Copy link
Member

aaime commented Sep 30, 2021

@bjornharrtell I'm just as clueless as you are... internet searches are giving me no hints either. Maybe ask on stackoverflow?

@bjornharrtell
Copy link
Contributor Author

@aaime I think I found that it's --illegal-access=debug that suppresses the problem. That's also a bit surprising since I thought the illegal-access flag was fully obsoleted in Java 17.

@bjornharrtell
Copy link
Contributor Author

I suppose --illegal-access=debug should be removed and all instances of setAccessible needs rework.. and then the SPI stuff. So lots of work. :)

@bjornharrtell
Copy link
Contributor Author

For reference, the obsoletion of illegal-access is described at https://openjdk.java.net/jeps/403.

@bjornharrtell
Copy link
Contributor Author

bjornharrtell commented Sep 30, 2021

@aaime I think I've gotten it to work using getImageWritersByMIMEType. But looks like it needs to be done for all SPI stuff. But I would like you to have a look at 2011657 before I make a more proper rework of all SPI usage.

@dromagnoli
Copy link
Contributor

@aaime I think I've gotten it to work using getImageWritersByMIMEType. But looks like it needs to be done for all SPI stuff. But I would like you to have a look at 2011657 before I make a more proper rework of all SPI usage.

I think it looks good.

@bjornharrtell
Copy link
Contributor Author

Finished for ImageWriter for 24862d9.

But then there are test failures for the other way round in imagemosaic. And I find that imagemosaic properties point directly to internal Spi classes.. that seems a bit unfortunate (fx

)

@bjornharrtell
Copy link
Contributor Author

bjornharrtell commented Oct 1, 2021

Found that I could use ServiceRegistry.lookupProviders to resolve ImageReaderSpi from classnames. I guess it could be used to resolve ImageWriterSpi too.. instead of using ImageIO.getImageWritersByMIMEType. Opinions?

@bjornharrtell
Copy link
Contributor Author

bjornharrtell commented Oct 1, 2021

A blocker to complete this might be that we need to wait for a newer release of easymock. (easymock/easymock#274)

Update: Might be working after all, that issue is probably not about general usage.

@bjornharrtell
Copy link
Contributor Author

I've reworked the SPI stuff to be a less intrusive change.

* Add GitHub action to verify build with Java 17 (early access)
* Rework to avoid use of reflection to override static final in testcase
* Avoid SimpleDateFormat ParseException by not using Locale.CANADA
* Handle XML prettify test result comparison issue
* Upgrade Mockito
@bjornharrtell
Copy link
Contributor Author

Downstream integration build fails because it also uses an illegal trick to override a static final field in testing. It needs to adapt to use the new public API introduced here, done with geoserver/geoserver#5319.

@bjornharrtell
Copy link
Contributor Author

bjornharrtell commented Oct 2, 2021

I actually think this is complete now but needs local verification and coordinated merge with geoserver. @aaime and/or @dromagnoli are you interested in doing that?

@aaime
Copy link
Member

aaime commented Oct 2, 2021

Wow great job, all tests with GeoTools passing! The downstream issue might indicate a change in behavior vs image reader choice (just guessing, I'm currently in bed with fever, writing from my phone)

@aaime
Copy link
Member

aaime commented Oct 2, 2021

Oh ok, now saw the other comments about the downstream build.

@aaime
Copy link
Member

aaime commented Oct 3, 2021

Did a quick pass... I have a hesitation about all the "add opens" added for tests. Maybe they are all for test code that does reflection things, but at the same time, don't they cover any other java 17 runtime issue? Tests are the only place where the code is actually run....

@bjornharrtell
Copy link
Contributor Author

@aaime yes, ideally tests should not require those add opens to verify other potential runtime issues with illegal reflection. But I think the amount of work to refactor them into something that can work without that is very large/difficult.

@aaime
Copy link
Member

aaime commented Oct 3, 2021

I'm smelling a trap here! But I also so much want to stick my nose into it!

Found this actual issue... JAI cannot work on Java 17 without an export:

java.lang.IllegalAccessError: class javax.media.jai.RasterAccessor (in unnamed module @0x9e89d68) cannot access class sun.awt.image.BytePackedRaster (in module java.desktop) because module java.desktop does not export sun.awt.image to unnamed module @0x9e89d68

	at javax.media.jai.RasterAccessor.<init>(RasterAccessor.java:802)
	at com.sun.media.jai.opimage.CopyOpImage.computeRect(CopyOpImage.java:68)
	at javax.media.jai.PointOpImage.computeTile(PointOpImage.java:914)
	at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:904)
	at javax.media.jai.OpImage.getTile(OpImage.java:1129)
	at javax.media.jai.PointOpImage.computeTile(PointOpImage.java:911)
	at com.sun.media.jai.util.SunTileScheduler.scheduleTile(SunTileScheduler.java:904)
	at javax.media.jai.OpImage.getTile(OpImage.java:1129)
	at javax.media.jai.RenderedOp.getTile(RenderedOp.java:2257)
	at org.geotools.image.ImageWorkerTest.testAlphaRGB(ImageWorkerTest.java:1256)
	at org.geotools.image.ImageWorkerTest.testOpacityAlphaRGBDirect(ImageWorkerTest.java:1185)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)

Long term I guess we can migrate to ImageN, and fix the direct reference to BytePackedRaster there... but I'm guessing it's just going to be one of many others.

So that one justifies the export. About the opens, two are needed for EasyMock, one for Parboiled, one I guess for an old version of EHCache we're using in s3-geotiff. Pushed a commit that makes these open a bit more surgical, and comments on the origin of the need.

@bjornharrtell
Copy link
Contributor Author

@aaime, makes sense and also makes it a bit more clear what might be long term goals.

@bjornharrtell
Copy link
Contributor Author

@aaime, I'm actually right now running GeoServer through IntelliJ IDEA and the org.geoserver.web.Start entry point with Java 17 + local GeoTools compilation pointing at the release configuration and things actually seem to work including WMS, WFS 2.0.0 and raster mosiac sources.

@@ -129,7 +129,7 @@
public static final String CONVERT_AXIS_KM_KEY =
"org.geotools.coverage.io.netcdf.convertAxis.km";

private static final boolean CONVERT_AXIS_KM;
private static boolean convertAxisKm;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just thinking out loud here... if the name is left as CONVERT_AXIS_KM then the existing code in GeoServer would not need to be changed right? This would allow starting to merge the GeoTools part without having to go and also have a GeoServer PR ready (the GeoServer PR for Java 17 would still use the new setter).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that would work because the removal of final

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't have to finish/merge the full GeoServer Java 17 thing, could just merge this specific one in coordination - geoserver/geoserver#5319.

@aaime
Copy link
Member

aaime commented Oct 16, 2021

Re-build everything with Java 8, indeed both builds pass. Merging. Thanks!

@aaime aaime merged commit 4c8b873 into geotools:main Oct 16, 2021
@bjornharrtell bjornharrtell deleted the java17 branch October 16, 2021 08:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants