JAXBElement: providing codec (/converter?) for class java.lang.Class

人走茶凉 提交于 2019-12-23 16:37:03

问题


I have been evaluating to adopt spring-data-mongodb for a project. In summary, my aim is:

  1. Using existing XML schema files to generate Java classes.
    • This is achieved using JAXB xjc
    • The root class is TSDProductDataType and is further modeled as below:

The thing to note here is that ExtensionType contains protected List<Object> any; allowing it to store Objects of any class. In my case, it is amongst the classes named TSDModule_Name_HereModuleType and can be browsed here

  1. Use spring-data-mongodb as persistence store

    • This is achieved using a simple ProductDataRepository

      @RepositoryRestResource(collectionResourceRel = "product", path = "product")
      public interface ProductDataRepository extends MongoRepository<TSDProductDataType, String> {
          TSDProductDataType queryByGtin(@Param("gtin") String gtin);
      }
      
    • The unmarshalled TSDProductDataType, however, contains JAXBElement which spring-data-mongodb doesn't seem to handle by itself and throws a CodecConfigurationException org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class java.lang.Class.

Here is the faulty statement:

TSDProductDataType tsdProductDataType = jaxbElement.getValue();
repository.save(tsdProductDataType);

I tried playing around with Converters for spring-data-mongodb as explained here, however, it seems I am missing something since the exception is about "Codecs" and not "Converters".

Any help is appreciated.

EDIT:

Adding converters for JAXBElement

Note: Works with version 1.5.6.RELEASE of org.springframework.boot::spring-boot-starter-parent. With version 2.0.0.M3, hell breaks loose

It seems that I missed something while trying to add converter earlier. So, I added it like below for testing:

@Component
@ReadingConverter
public class JAXBElementReadConverter implements Converter<DBObject, JAXBElement> {
    //@Autowired
    //MongoConverter converter;

    @Override
    public JAXBElement convert(DBObject dbObject) {
        Class declaredType, scope;
        QName name = qNameFromString((String)dbObject.get("name"));
        Object rawValue = dbObject.get("value");
        try {
            declaredType = Class.forName((String)dbObject.get("declaredType"));
        } catch (ClassNotFoundException e) {
            if (rawValue.getClass().isArray()) declaredType = List.class;
            else declaredType = LinkedHashMap.class;
        }
        try {
            scope = Class.forName((String) dbObject.get("scope"));
        } catch (ClassNotFoundException e) {
            scope = JAXBElement.GlobalScope.class;
        }
        //Object value = rawValue instanceof DBObject ? converter.read(declaredType, (DBObject) rawValue) : rawValue;
        Object value = "TODO";
        return new JAXBElement(name, declaredType, scope, value);
    }

    QName qNameFromString(String s) {
        String[] parts = s.split("[{}]");
        if (parts.length > 2) return new QName(parts[1], parts[2], parts[0]);
        if (parts.length == 1) return new QName(parts[0]);
        return new QName("undef");
    }
}


@Component
@WritingConverter
public class JAXBElementWriteConverter implements Converter<JAXBElement, DBObject> {
    //@Autowired
    //MongoConverter converter;

    @Override
    public DBObject convert(JAXBElement jaxbElement) {
        DBObject dbObject = new BasicDBObject();
        dbObject.put("name", qNameToString(jaxbElement.getName()));
        dbObject.put("declaredType", jaxbElement.getDeclaredType().getName());
        dbObject.put("scope", jaxbElement.getScope().getCanonicalName());
        //dbObject.put("value", converter.convertToMongoType(jaxbElement.getValue()));
        dbObject.put("value", "TODO");
        dbObject.put("_class", JAXBElement.class.getName());
        return dbObject;
    }

    public String qNameToString(QName name) {
        if (name.getNamespaceURI() == XMLConstants.NULL_NS_URI) return name.getLocalPart();
        return name.getPrefix() + '{' + name.getNamespaceURI() + '}' + name.getLocalPart();
    }
}


@SpringBootApplication
public class TsdApplication {

    public static void main(String[] args) {
        SpringApplication.run(TsdApplication.class, args);
    }

    @Bean
    public CustomConversions customConversions() {
        return new CustomConversions(Arrays.asList(
                new JAXBElementReadConverter(),
                new JAXBElementWriteConverter()
        ));
    }
}

So far so good. However, how do I instantiate MongoConverter converter;? MongoConverter is an interface so I guess I need an instantiable class adhering to this interface. Any suggestions?


回答1:


I understand the desire for convenience in being able to just map an existing domain object to the database layer with no boilerplate, but even if you weren't having the JAXB class structure issue, I would still be recommending away from using it verbatim. Unless this is a simple one-off project, you almost definitely will hit a point where your domain models will need to change but your persisted data need to remain in an existing state. If you are just straight persisting the data, you have no mechanism to convert between a newer domain schema and an older persisted data scheme. Versioning of the persisted data scheme would be wise too.

The link you posted for writing the customer converters is one way to achieve this and fits in nicely with the Spring ecosystem. That method should also solve the issue you are experiencing (about the underlying messy JAXB data structure not converting cleanly).

Are you unable to get that method working? Ensure you are loading them into the Spring context with @Component plus auto-class scanning or manually via some Configuration class.

EDIT to address your EDIT:

Add the following to each of your converters:

private final MongoConverter converter;

public JAXBElement____Converter(MongoConverter converter) {
    this.converter = converter;
}

Try changing your bean definition to:

@Bean
public CustomConversions customConversions(@Lazy MongoConverter converter) {
    return new CustomConversions(Arrays.asList(
            new JAXBElementReadConverter(converter),
            new JAXBElementWriteConverter(converter)
    ));
}


来源:https://stackoverflow.com/questions/46023866/jaxbelement-providing-codec-converter-for-class-java-lang-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!