Simon
Simon

Reputation: 19928

Why does reading embedded documents with Spring Data MongoDB fail if the property is typed to Object?

Lets say I have the following Spring Data MongoDB interface:

public interface AccountRepository extends MongoRepository<Account, String> {}

Now the object Account is displayed below:

public class Account {

    @Id
    private String id;
    private Authentication authentication; //this is from the interface org.springframework.security.core.Authentication. Here I'm using org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken which implements the interface

When I call the below, Jackson will not save the account object as I do not have a model for Authentication that it can use to map the inner Authentication object. Furthermore, Jackson requires that there be a public empty constructor of Authentication() and the Spring Authentication class does not have such a constructor:

 @Autowired
 AccountRepository accountRepo;

accountRepo.save(Account);

I had to change the account model to the following to save Authentication as an Object:

public class Account {

    @Id
    private String id;
    private Object authentication;

However, when I try to retrieve the authentication object and recast it to an Authentication object, I get this error:

 {
  "timestamp": 1438441062525,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "org.springframework.data.mapping.model.MappingException",
  "message": "No property aPrincipal found on entity class com.futureprocessing.spring.infrastructure.AuthenticatedExternalWebService to bind constructor parameter to!",
  "path": "/account"
}

I can always save the pieces of Authentication separately in my account object (Principal, Credentials and Grant Authorities) but then I will have to do a constructor to recreate it if it is needed.

Is there no easier way to just save an Authentication object as an Authentication object?

Upvotes: 1

Views: 1181

Answers (1)

Oliver Drotbohm
Oliver Drotbohm

Reputation: 83051

tl;dr

Due to the change in the object model, and the previous version using stronger types, the already existing documents in the database to not contain any type information. This causes Spring Data MongoDB understandably fail to derive that you want to read an embedded Authentication when all it can reason about is a property of type Object.

Details

The issue here is that your database doesn't contain any type information as you stored documents derived from the class model when the authentication property still was an Authentication. If at storage time, the value of the property is of the exact same type as the declared property, Spring Data MongoDB doesn't write any type information to avoid additional overhead.

If you persist an Account with the authentication property declared as object but containing an Authentication object, Spring Data MongoDB will add an _class property to the embedded document capturing the type information, so that when reading the document the mapper knows which type to instantiate.

Without that additional property in the document and the authentication property in the class of type Object, how would the mapper know you'd want to instantiate an Authentication object when reading a document?

Solutions

The easiest way to fix this is by manually issuing an update to the database, adding the missing type information. By default we're using fully qualified class names so that something like this should do the trick:

Update setTypeInformation = Update.update("authentication._class", Authentication.class.getName());
template.updateMulti(new Query(), setTypeInformation, Account.class);

The Update statement adds the _class property to the embedded authentication document and sets it to the fully qualified name of Authentication. The Update is the executed against all Accounts due to the empty Querywhich matches all existing documents.

Further references

More information on this can be found in the corresponding section of the reference documentation.

Upvotes: 1

Related Questions