Jooq: ImmutablePOJOMapperWithParameterNames throws InaccessibleObjectException on module path

Created on 10 Jun 2020  路  13Comments  路  Source: jOOQ/jOOQ

I'm receiving an InaccessibleObjectException while attempting to map jOOQ records to immutable POJO's which have public constructors annotated with @ConstructorProperties.

The target POJO's are part of a shared data model in a JPMS module which is not aware of, nor obviously open to, jOOQ.

Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.lang.String com.my.immutable.data.model.MyImmutablePOJO accessible: module com.my.immutable.data.model does not "opens com.my.immutable.data.model" to module org.jooq
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:349)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:289)
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:174)
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:168)
    at [email protected]/org.jooq.tools.reflect.Reflect.accessible(Reflect.java:279)
    at [email protected]/org.jooq.impl.Tools$7.apply(Tools.java:3486)
    at [email protected]/org.jooq.impl.Tools$7.apply(Tools.java:3474)
    at [email protected]/org.jooq.impl.Tools$Cache.run(Tools.java:3201)
    at [email protected]/org.jooq.impl.Tools.getMatchingMembers(Tools.java:3474)
    at [email protected]/org.jooq.impl.DefaultRecordMapper$ImmutablePOJOMapperWithParameterNames.<init>(DefaultRecordMapper.java:935)
    at [email protected]/org.jooq.impl.DefaultRecordMapper.init(DefaultRecordMapper.java:381)
    at [email protected]/org.jooq.impl.DefaultRecordMapper.<init>(DefaultRecordMapper.java:315)
    at [email protected]/org.jooq.impl.DefaultRecordMapper.<init>(DefaultRecordMapper.java:306)
    at [email protected]/org.jooq.impl.DefaultRecordMapperProvider$1.apply(DefaultRecordMapperProvider.java:86)
    at [email protected]/org.jooq.impl.DefaultRecordMapperProvider$1.apply(DefaultRecordMapperProvider.java:83)
    at [email protected]/org.jooq.impl.Tools$Cache.run(Tools.java:3201)
    at [email protected]/org.jooq.impl.DefaultRecordMapperProvider.provide(DefaultRecordMapperProvider.java:83)
    at [email protected]/org.jooq.impl.AbstractRecord.into(AbstractRecord.java:731)

This is because ImmutablePOJOMapperWithParameterNames first attempts to match the record against private member fields (and make those accessible), despite the existence of the @ConstructorProperties mapping for all members, which it then proceeds to use anyway.

Functionality All Editions Medium Fixed Defect

All 13 comments

I have worked around this issue by implementing an alternative RecordMapper, which simply invokes the public constructor, without attempting to modify any members to be accessible.

https://github.com/hubick/jooq-immutable-pojo-mapper

Thanks for your report. Even before JPMS, we should probably be more careful making things accessible. There could have been a SecurityManager.

What's the reason why you have a no-arg constructor in your immutable POJO? Can you show an example?

I'm sorry, I do not have a no-arg constructor. Was there something that implied I did?

Not you, but the logic where the exception is raised:

https://github.com/jOOQ/jOOQ/blob/version-3.13.2/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java#L362-L366

There should be no zero-arg constructor, so we should reach the catch (NoSuchMethodException ignore) {} block before calling any accessibles

Oh, I get it... the stack trace goes into ImmutablePOJOMapperWithParameterNames, where more accessible calls are being made. My bad...

Can be reproduced easily in a modular project with:

package org.jooq.test.modulepath.api;

import java.beans.ConstructorProperties;

/**
 * [#10267] Make sure we don't try to make properties accessible
 */
public class ImmutablePojoWithConstructorProperties {

    private final Integer a;
    private final String  b;

    @ConstructorProperties({ "a", "b" })
    public ImmutablePojoWithConstructorProperties(Integer a, String b) {
        this.a = a;
        this.b = b;
    }

    public Integer getA() {
        return a;
    }

    public String getB() {
        return b;
    }
}

And then:

package org.jooq.test.modulepath.tests;

import static org.jooq.SQLDialect.DEFAULT;
import static org.jooq.impl.DSL.field;
import static org.jooq.impl.DSL.unquotedName;
import static org.jooq.impl.SQLDataType.INTEGER;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.junit.Assert.assertEquals;

import org.jooq.DSLContext;
import org.jooq.Field;
import org.jooq.impl.DSL;
import org.jooq.test.modulepath.api.ImmutablePojoWithConstructorProperties;

import org.junit.Before;
import org.junit.Test;

public class ImmutablePojoWithConstructorPropertiesTests {

    private Field<Integer> a;
    private Field<String> b;
    private DSLContext    ctx;

    @Before
    public void setup() {
        this.a = field(unquotedName("a"), INTEGER);
        this.b = field(unquotedName("b"), VARCHAR);
        this.ctx = DSL.using(DEFAULT);
    }

    @Test
    public void testImmutablePOJOMapperWithParameterNames() {
        ImmutablePojoWithConstructorProperties result = ctx.newRecord(a, b).values(1, "a").into(ImmutablePojoWithConstructorProperties.class);

        assertEquals(1, (int) result.getA());
        assertEquals("a", result.getB());
    }
}

producing:

java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.lang.Integer org.jooq.test.modulepath.api.ImmutablePojoWithConstructorProperties.a accessible: module org.jooq.test.modulepath.api does not "opens org.jooq.test.modulepath.api" to module org.jooq
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:340)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:280)
    at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
    at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
    at org.jooq/org.jooq.tools.reflect.Reflect.accessible(Reflect.java:279)
    at org.jooq/org.jooq.impl.Tools$29.apply(Tools.java:3504)
    at org.jooq/org.jooq.impl.Tools$29.apply(Tools.java:1)
    at org.jooq/org.jooq.impl.Tools$Cache.run(Tools.java:3219)
    at org.jooq/org.jooq.impl.Tools.getMatchingMembers(Tools.java:3492)
    at org.jooq/org.jooq.impl.DefaultRecordMapper$ImmutablePOJOMapperWithParameterNames.<init>(DefaultRecordMapper.java:1062)
    at org.jooq/org.jooq.impl.DefaultRecordMapper.init(DefaultRecordMapper.java:392)
    at org.jooq/org.jooq.impl.DefaultRecordMapper.<init>(DefaultRecordMapper.java:321)
    at org.jooq/org.jooq.impl.DefaultRecordMapper.<init>(DefaultRecordMapper.java:312)
    at org.jooq/org.jooq.impl.DefaultRecordMapperProvider$1.apply(DefaultRecordMapperProvider.java:86)
    at org.jooq/org.jooq.impl.DefaultRecordMapperProvider$1.apply(DefaultRecordMapperProvider.java:1)
    at org.jooq/org.jooq.impl.Tools$Cache.run(Tools.java:3219)
    at org.jooq/org.jooq.impl.DefaultRecordMapperProvider.provide(DefaultRecordMapperProvider.java:83)
    at org.jooq/org.jooq.impl.AbstractRecord.into(AbstractRecord.java:734)
    at org.jooq.test.modulepath.api/org.jooq.test.modulepath.tests.ImmutablePojoWithConstructorPropertiesTests.testImmutablePOJOMapperWithParameterNames(ImmutablePojoWithConstructorPropertiesTests.java:70)

It's impossible to say if anyone out there would be relying on a combination of private field injection for some fields and @ConstructorProperties for others (_shudder_), and I don't know what algorithm you intend to use in the presence of @ConstructorProperties to decide if you will still attempt any private field injection, _if at all_. But I would suggest that, in the presence of a @ConstructorProperties annotation, if you do decide to still make any attempts at modifying private field access, that any failure of those operations should not cause the entire object construction to be aborted, but rather, just a fallback to simply invoking the annotated constructor with whatever mappings it does contain.

It's impossible to say if anyone out there would be relying on a combination of private field injection for some fields and @ConstructorProperties for others (_shudder_)

Good points, I think we can rule that out.

Nevertheless, there are two things that need to be done here.

  1. Implement your suggestions in the presence of @ConstructorProperties
  2. Make sure no such exception is thrown in the absence of @ConstructorProperties

E.g. if the POJO is like this for whatever reason:

public class X {
  public int a;
  private int b;
}

We should think about what to do with b - continue to fail with InaccessibleObjectException, or just ignore b. Perhaps a new setting could be introduced for this: https://github.com/jOOQ/jOOQ/issues/10341

Anyway, this bug isn't because we want to make the fields accessible in the presence of @ConstructorProperties. We just do that always, irrespective of how the POJO is mapped, because the getMatchingMembers() method is reused for different purposes:

image

Clearly, making things accessible should be optional

On the other hand, the constructor is invoked reflectively, so you have to add an opens <your module> to org.jooq; declaration anyway, which in turn, allows for making your members accessible.

So, perhaps, this is just a misleading exception at the wrong place?

Nevermind. For that to work, exports <your module>; is sufficient. No need to open it.

Fixed in jOOQ 3.14.0. Backport scheduled for 3.13.3 (#10342).

Thanks again for reporting!

Was this page helpful?
0 / 5 - 0 ratings