8

Consider the following entities:

package br.com.investors.domain.endereco;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import org.hibernate.validator.constraints.NotBlank;

import javax.persistence.*;
import java.io.Serializable;

import static com.google.common.base.Preconditions.checkArgument;
import static javax.persistence.GenerationType.SEQUENCE;

@Entity
public class Regiao implements Serializable, Comparable<Regiao> {

    @Id
    @GeneratedValue(strategy = SEQUENCE)
    private Long id;

    @Version
    private Long version;

    @NotBlank
    @Column(length = 100, unique = true)
    private String nome = "";

    Regiao() {}

    public Regiao(String nome) {
        checkArgument(!Strings.isNullOrEmpty(nome), "Nome não pode ser vazio");
        this.nome = nome;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Regiao) {
            Regiao o = (Regiao) obj;
            return Objects.equal(this.nome, o.nome);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(nome);
    }

    @Override
    public int compareTo(Regiao o) {
        return ComparisonChain.start()
                .compare(this.nome, o.nome)
                .result();
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(getClass()).add("nome", nome).toString();
    }

    public Long getId() {
        return id;
    }

    public Long getVersion() {
        return version;
    }

    public String getNome() {
        return nome;
    }
}

and

package br.com.investors.domain.endereco;

import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import org.hibernate.validator.constraints.NotBlank;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static javax.persistence.GenerationType.SEQUENCE;

@Entity
public class Cidade implements Serializable, Comparable<Cidade> {

    @Id
    @GeneratedValue(strategy = SEQUENCE)
    private Long id;

    @Version
    private Long version;

    @NotBlank
    @Column(length = 100, unique = true)
    private String nome = "";

    @NotNull
    @ManyToOne
    private Regiao regiao;

    @NotNull
    @ManyToOne
    private Estado estado;

    Cidade() {}

    public Cidade(String nome, Regiao regiao, Estado estado) {
        checkArgument(!Strings.isNullOrEmpty(nome), "Nome não pode ser vazio");
        checkNotNull(regiao, "Região não pode ser nulo");
        checkNotNull(estado, "Estado não pode ser nulo");

        this.nome = nome;
        this.regiao = regiao;
        this.estado = estado;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Cidade) {
            Cidade o = (Cidade) obj;
            return Objects.equal(this.nome, o.nome) &&
                    Objects.equal(this.estado, o.estado) &&
                    Objects.equal(this.regiao, o.regiao);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(nome, regiao, estado);
    }

    @Override
    public int compareTo(Cidade o) {
        return ComparisonChain.start()
                .compare(this.estado, o.estado)
                .compare(this.regiao, o.regiao)
                .compare(this.nome, o.nome)
                .result();
    }

    @Override
    public String toString() {
        return Objects.toStringHelper(getClass()).add("nome", nome).add("regiao", regiao).add("estado", estado).toString();
    }

    public Long getId() {
        return id;
    }

    public Long getVersion() {
        return version;
    }

    public String getNome() {
        return nome;
    }

    public Regiao getRegiao() {
        return regiao;
    }

    public Estado getEstado() {
        return estado;
    }
}

I'm trying to POST a JSON to a RestController

@RequestMapping(value = "/cidades", method = POST, consumes = APPLICATION_JSON_VALUE)
void inserir(@RequestBody Cidade cidade) {
    repository.save(cidade);
}

I'm using default configurations of Spring Boot to serialize and deserialize objects.

If I post a JSON like this, it works fine:

{
    "nome": "Cidade",
    "regiao": "/10"
}

But I need to post a JSON like this:

{
    "nome": "Cidade",
    "regiao": {
        "id": 10,
        "version": 0,
        "nome": "regiao"
    }
}

If I do so, I get the error

{
    "timestamp": "2015-04-02",
    "status": 400,
    "error": "Bad Request",
    "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
    "message": "Could not read JSON: Template must not be null or empty! (through reference chain: br.com.investors.domain.endereco.Cidade[\"regiao\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Template must not be null or empty! (through reference chain: br.com.investors.domain.endereco.Cidade[\"regiao\"])",
    "path": "/cidades/"
}

Doing some debug, I found that Jackson tries to create a URI from the "regiao" property of the posted object, waiting for a string template like "/{id}". I'm googling it but can't find a properly answer for this.

I saw some related issues on StackOverflow but none worked for me.

Can you guys say what is the matter of this?

I think that is just a configuration but don't know how or where.

I'm also trying to avoid custom serializers and deserializers.

EDIT:

If I POST a JSON with only the ids of the nested entities, like this:

{
  "nome": "Cidade",
  "estado": "10",
  "regiao": "10"
}

I get this message:

{
    "timestamp": "2015-04-07",
    "status": 400,
    "error": "Bad Request",
    "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
    "message": "Could not read JSON: Failed to convert from type java.net.URI to type br.com.investors.domain.endereco.Estado for value '10'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 10. Is it local or remote? Only local URIs are resolvable. (through reference chain: br.com.investors.domain.endereco.Cidade[\"estado\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type java.net.URI to type br.com.investors.domain.endereco.Estado for value '10'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI 10. Is it local or remote? Only local URIs are resolvable. (through reference chain: br.com.investors.domain.endereco.Cidade[\"estado\"])",
    "path": "/cidades"
}

As I see that the correct way of send the nested entity is like "regiao": "/10", I'm hardcoding this in my Javascript to workaround:

function(item) {
    item.regiao = "/" + item.regiao.id; //OMG
    item.estado = "/" + item.estado.id; //OMG!!

    if (item.id) {
        return $http.put('/cidades/' + item.id, item);
    } else {
        return $http.post('/cidades', item);
    }
}

It works but it sucks. How can I fix this in Javascript or configuring Jackson?

Reading some docs, there is something to do with UriToEntityConverter, but still don't know the correct way of configure this.

Thanks.

  • Can you post getter and setters for the relavant fields, i.e. `regiao` in `Cidadae` and attributes of `Regiao` you're trying to post? – ci_ Apr 03 '15 at 09:11
  • @ci_ entities updated. Now you can see the full classes. – Jonathan Oliveira Apr 06 '15 at 13:27
  • How do you set the version and id fields? There are no constructors or setters for them. Try adding setters for all your field members. There are ways to make it work without, but see if adding setters works first. – ci_ Apr 06 '15 at 13:43
  • @ci_ The `version` and `id` are created and updated by Hibernate. Even with getters and setter for all fields in all classes I get the bad request error. At least when I create a new Regiao, I POST a JSON with only `nome` attribute. – Jonathan Oliveira Apr 06 '15 at 19:04
  • in Cidade you have the defined Estado as NotNull and if you look at the error, it says "Failed to convert from type java.net.URI to type br.com.investors.domain.endereco.Estado" try to use JsonIgnoreProperties in your Cidade class or see if you remove NotNull if it will solve the issue – faljbour Apr 07 '15 at 05:27
  • Do you use spring-data-rest by any chance? Just googled the exact error and came up with this http://stackoverflow.com/questions/26768897/how-to-add-book-to-shelves-1-books This may not have anything to do with Jackson deserialization in the end. – ci_ Apr 07 '15 at 08:08
  • Yes, I do. Looking at the Spring Data code [I found this.](https://github.com/spring-projects/spring-data-rest/blob/master/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/UriToEntityConverter.java#L111) I think that my problem is actually how to threat this in AngularJS code... – Jonathan Oliveira Apr 07 '15 at 16:54

2 Answers2

4

I solved it with @RestResource(exported = false) annotation on the EstadoRepository and RegiaoRepository classes.

It "hides" de repo from spring when it's auto config the endpoints and stuff...

0

You can use @JsonIgnoreProperties(ignoreUnknown = true) annotation on your entity class like this.

@Entity
@JsonIgnoreProperties(ignoreUnknown = true)
public class Area implements Serializable, CompanyAware, IdentifiableModel<Long> {
private static long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id = 0l;
@NotNull
@NotEmpty
@Column(nullable = false, unique = true)
private String name;
@NotNull
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(nullable = false)
private Region region;
private boolean active = true;

@ManyToOne
@JoinColumn(updatable = false)
private Company company;

public Long getId() {
    return id;
}

public void setId(Long id) {
    this.id = id;
}

@Override
public int hashCode() {
    int hash = 0;
    hash += (getId() != null ? getId().hashCode() : 0);
    return hash;
}

@Override
public boolean equals(Object object) {
    // TODO: Warning - this method won't work in the case the id fields are not set
    if (!(object instanceof Area)) {
        return false;
    }
    Area other = (Area) object;
    if ((this.getId() == null && other.getId() != null) || (this.getId() != null && !this.id.equals(other.id))) {
        return false;
    }
    return true;
}

@Override
public String toString() {
    return "Area[ id=" + getId() + " ]";
}

/**
 * @return the name
 */
public String getName() {
    return name;
}

/**
 * @param name the name to set
 */
public void setName(String name) {
    this.name = name;
}

/**
 * @return the region
 */
public Region getRegion() {
    return region;
}

/**
 * @param region the region to set
 */
public void setRegion(Region region) {
    this.region = region;
}

/**
 * @return the active
 */
public boolean isActive() {
    return active;
}

/**
 * @param active the active to set
 */
public void setActive(boolean active) {
    this.active = active;
}

/**
 * @return the company
 */
public Company getCompany() {
    return company;
}

/**
 * @param company the company to set
 */
public void setCompany(Company company) {
    this.company = company;
} 
}

It may solve your problem. It will Ignore unknown missing fields in json object. It will use only available fields in json object and ignore unknown fields.

sohan nohemy
  • 615
  • 5
  • 13
  • That does not work for the problem. It is something to do with how to change the Jackson's ConditionalGenericConverter on Spring Boot. I'm reading about it, look some codes, but no result yet :( – Jonathan Oliveira May 27 '15 at 17:51