I am currently updating my ElasticSearch FieldMapper plugin for accepting a TokenStream in JSON format to ES version 7.17.
I copied most of it from the TextFieldPlugin
because the Preanalyzed
format is meant to contain longer text, but completely preanalyzed as a TokenStream.
All tests work fine. However, when I deploy the plugin in an ElasticSearch server, I get an IllegalAccessException
when trying to create an index that contains a preanalyzed
field, thus, engages the PreanalyzedFieldMapper
:
java.lang.IllegalAccessError: class org.elasticsearch.index.mapper.PreanalyzedFieldMapper tried to access protected method 'void org.elasticsearch.index.mapper.FieldMapper$Parameter.toXContent(org.elasticsearch.xcontent.XContentBuilder, boolean)' (org.elasticsearch.index.mapper.PreanalyzedFieldMapper is in unnamed module of loader java.net.FactoryURLClassLoader @652b3733; org.elasticsearch.index.mapper.FieldMapper$Parameter is in unnamed module of loader 'app')
at org.elasticsearch.index.mapper.PreanalyzedFieldMapper.doXContentBody(PreanalyzedFieldMapper.java:1255)
at org.elasticsearch.index.mapper.FieldMapper.toXContent(FieldMapper.java:434)
at org.elasticsearch.index.mapper.ObjectMapper.serializeMappers(ObjectMapper.java:431)
at org.elasticsearch.index.mapper.ObjectMapper.toXContent(ObjectMapper.java:416)
at org.elasticsearch.index.mapper.ObjectMapper.toXContent(ObjectMapper.java:394)
at org.elasticsearch.index.mapper.ObjectMapper.serializeMappers(ObjectMapper.java:431)
at org.elasticsearch.index.mapper.ObjectMapper.toXContent(ObjectMapper.java:416)
at org.elasticsearch.index.mapper.Mapping.toXContent(Mapping.java:176)
at org.elasticsearch.common.compress.CompressedXContent.<init>(CompressedXContent.java:96)
at org.elasticsearch.index.mapper.Mapping.toCompressedXContent(Mapping.java:77)
at org.elasticsearch.index.mapper.DocumentMapper.<init>(DocumentMapper.java:35)
at org.elasticsearch.index.mapper.MapperService.newDocumentMapper(MapperService.java:460)
at org.elasticsearch.index.mapper.MapperService.applyMappings(MapperService.java:440)
at org.elasticsearch.index.mapper.MapperService.mergeAndApplyMappings(MapperService.java:421)
at org.elasticsearch.index.mapper.MapperService.merge(MapperService.java:402)
at org.elasticsearch.cluster.metadata.MetadataCreateIndexService.updateIndexMappingsAndBuildSortOrder(MetadataCreateIndexService.java:1303)
at org.elasticsearch.cluster.metadata.MetadataCreateIndexService.lambda$applyCreateIndexWithTemporaryService$3(MetadataCreateIndexService.java:453)
at org.elasticsearch.indices.IndicesService.withTempIndexService(IndicesService.java:673)
at org.elasticsearch.cluster.metadata.MetadataCreateIndexService.applyCreateIndexWithTemporaryService(MetadataCreateIndexService.java:451)
at org.elasticsearch.cluster.metadata.MetadataCreateIndexService.applyCreateIndexRequestWithV1Templates(MetadataCreateIndexService.java:567)
at org.elasticsearch.cluster.metadata.MetadataCreateIndexService.applyCreateIndexRequest(MetadataCreateIndexService.java:413)
at org.elasticsearch.cluster.metadata.MetadataCreateIndexService.applyCreateIndexRequest(MetadataCreateIndexService.java:420)
at org.elasticsearch.cluster.metadata.MetadataCreateIndexService$1.execute(MetadataCreateIndexService.java:319)
at org.elasticsearch.cluster.ClusterStateUpdateTask.execute(ClusterStateUpdateTask.java:51)
at org.elasticsearch.cluster.service.MasterService.executeTasks(MasterService.java:840)
at org.elasticsearch.cluster.service.MasterService.calculateTaskOutputs(MasterService.java:407)
at org.elasticsearch.cluster.service.MasterService.runTasks(MasterService.java:243)
at org.elasticsearch.cluster.service.MasterService.access$100(MasterService.java:63)
at org.elasticsearch.cluster.service.MasterService$Batcher.run(MasterService.java:170)
at org.elasticsearch.cluster.service.TaskBatcher.runIfNotProcessed(TaskBatcher.java:146)
at org.elasticsearch.cluster.service.TaskBatcher$BatchedTask.run(TaskBatcher.java:202)
at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:718)
at org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.runAndClean(PrioritizedEsThreadPoolExecutor.java:262)
at org.elasticsearch.common.util.concurrent.PrioritizedEsThreadPoolExecutor$TieBreakingPrioritizedRunnable.run(PrioritizedEsThreadPoolExecutor.java:225)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1589)
Suppressed: java.lang.IllegalStateException: Failed to close the XContentBuilder
at org.elasticsearch.xcontent.XContentBuilder.close(XContentBuilder.java:1131)
at org.elasticsearch.common.compress.CompressedXContent.<init>(CompressedXContent.java:92)
... 28 more
Caused by: java.io.IOException: Unclosed object or array found
at org.elasticsearch.xcontent.json.JsonXContentGenerator.close(JsonXContentGenerator.java:456)
at org.elasticsearch.xcontent.XContentBuilder.close(XContentBuilder.java:1129)
... 29 more
The issue seems to be that my method doXContentBody()
- which is really copied from the TextFieldMapper
- tries to serialize its Parameter objects that are instances of an inner class in the mapper super class FieldMapper
and have the protected
method toXContent
that is called in "my" method to serialize the whole mapper with its parameters.
Now I know why this doesn't work: My plugin lies in its own JAR file apart from the ElasticSearch core JARs and is loaded with another class loader. Thus, it does not have access to the protected fields and methods of the inner classes of its super class (Parameter
is an inner class of FieldMapper
).
My basic question here is: How is this meant to work? I tried to check out other ES Mapping Plugins but they just don't implement the doXContentBody()
method. I am unclear about the consequences about implementing or not implementing it and so I am at a loss here. Must I implement it? How could I do it without access to the toXContent()
method of the FieldMapper.Parameter
class? What happens if I don't implement it?
My plugin code and the specific method can be found here: https://github.com/JULIELab/elasticsearch-mapper-preanalyzed/blob/86bd2b5cf9f3020231603ed5cc7bfcdb3feb6669/src/main/java/org/elasticsearch/index/mapper/PreanalyzedFieldMapper.java#L1241
As I said, it's in large parts a copy of ElasticSearch's TextFieldMapper
class since it should behave like it. Perhaps that is an error to begin with. But in previous ES versions that worked fine. This issue with the protected parameter method is new to me.