Kyle Richter
Kyle Richter

Reputation: 406

Internal server error on Jackson @ResponseBody

I just want to return my user object as JSON for use by an ajax call on the client-side.

This was working at one point, and after some updates (namely, changing the application to deploy to / in Jetty), now it's not.

I'm not throwing an exception from the code, it is returning just fine, but seems to blow up somewhere in the root code in Jackson when trying to convert the object to JSON.

Like I said, I'm not getting an exception, my ajax call just blows up saying error code "500, Internal Server Error".

/* User contains information about a user of this site, that exists only
 * in the context of this site (no names, addresses).
 */
@Entity(name="User")
@Table(name="USER")
@NamedQuery(
    name="findUserByName",
    query="SELECT OBJECT(u) FROM User u WHERE u.name = :name"
)
public class User extends AuditableEntity implements Serializable {

private static final long serialVersionUID = -1308795024222223320L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="id")
private Long id;


@NotEmpty
@MinSkipEmpty(value=6)
@MaxSkipEmpty(value=32)
@Column(name="name", length=32)
private String name;

@NotEmpty
@MinSkipEmpty(value=4)
@MaxSkipEmpty(value=40)
@Column(name="password", length=40)
private String password;

@Column(name="salt", length=40)
private String salt;


@ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST})
@JoinColumn(name="person_id")
private Person person;

@Column(name="last_login")
private Date lastLogin;

@ElementCollection(fetch=FetchType.EAGER)
@CollectionTable(name ="USER_AUTHORITY")
@Column(name="authority")
private List<Integer> authorities;


public Long getId() {
    return id;
}

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

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = (name == null ? name : name.trim());

}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = (password == null ? password : password.trim());
}

public String getSalt() {
    return salt;
}

public void setSalt(String salt) {
    this.salt = salt;
}

public Person getPerson() {
    return person;
}

public void setPerson(Person person) {
    this.person = person;
}

public Date getLastLogin() {
    return lastLogin;
}

public void setLastLogin(Date lastLogin) {
    this.lastLogin = lastLogin;
}

public List<Integer> getAuthorities() {
    return authorities;
}

public void setAuthorities(List<Integer> authorities) {
    this.authorities = authorities;
}

}

This is the Person Entity

@Entity(name = "Person")
@Table(name = "PERSON")
public class Person extends AuditableEntity implements Serializable {

    private static final long serialVersionUID = -1308795024262635690L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @NotEmpty
    @MaxSkipEmpty(value=64)
    @Column(name = "firstName", length=64)
    private String firstName;

    @NotEmpty
    @MaxSkipEmpty(value=64)
    @Column(name = "lastName", length=64)
    private String lastName;

    @NotEmpty
    @Email
    @MaxSkipEmpty(value=256,  message="")
    @Column(name = "email", length=256)
    private String email;

    @DateTimeFormat(pattern="MM/dd/yyyy")
    @NotNull(message = "Required field")
    @Column(name = "date")
    private Date birthday;

    @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST })
    @JoinColumn(name = "location_id")
    private Location location;


    public Person() {

    }

    public Person(String firstName, String lastName) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public Long getId() {
        return id;
    }

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Location getLocation() {
        return location;
    }

    public void setLocation(Location location) {
        this.location = location;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {

        return super.toString() + " name = " + firstName + " " + lastName
                + " id = " + id;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((firstName == null) ? 0 : firstName.hashCode());
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result
                + ((lastName == null) ? 0 : lastName.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (firstName == null) {
            if (other.firstName != null)
                return false;
        } else if (!firstName.equals(other.firstName))
            return false;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (lastName == null) {
            if (other.lastName != null)
                return false;
        } else if (!lastName.equals(other.lastName))
            return false;
        return true;
    }

}

@Entity(name = "Location")
@Table(name = "LOCATION")
public class Location extends AuditableEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    //name of person/place/thing

    @Column(name = "name", length=128)
    String name;

    //street address, p.o. box, company name, c/o
    @NotEmpty
    @MaxSkipEmpty(value=128)
    @Column(name = "line_1", length=128)
    String line1;

    // apt., suite, building, floor, entrance, etc.
    @Column(name = "line_2", length=128)
    String line2;

    @NotEmpty
    @MaxSkipEmpty(value=64)
    @Column(name = "city", length=64)
    String city;

    // state, providence, region
    @NotEmpty
    @MaxSkipEmpty(value=40)
    @Column(name = "state", length=40)
    String state;

    // postal code
    @NotEmpty
    @MaxSkipEmpty(value=16)
    @Column(name = "zip", length=16)
    String zip;

    @Column(name = "country")
    String country;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLine1() {
        return line1;
    }

    public void setLine1(String line1) {
        this.line1 = line1;
    }

    public String getLine2() {
        return line2;
    }

    public void setLine2(String line2) {
        this.line2 = line2;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getZip() {
        return zip;
    }

    public void setZip(String zip) {
        this.zip = zip;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    private static final long serialVersionUID = -178898928354655555L;
}

@RequestMapping(value="user/{documentId}", method=RequestMethod.GET)
public @ResponseBody User getUserForDocument( Model model,     @PathVariable("documentId") Long docId){

    Document doc = null;
    try{
        doc = dService.find(docId);
    }catch(Exception e){
        Logger logger = Logger.getLogger(DocumentController.class);
        logger.error(e.getMessage());
    }
    User user = doc.getUser();

    user.getPerson();
    user.getPerson().getLocation();
    return user;
}

@Repository()
public class DocumentDaoImpl implements DocumentDao {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public Document find(Long id) {

        Document doc = entityManager.find(Document.class, id);

        Hibernate.initialize(doc.getUser());
        Hibernate.initialize(doc.getUser().getPerson());
        Hibernate.initialize(doc.getUser().getPerson().getLocation());

        return doc;
    }

    @SuppressWarnings("unchecked")
    @Transactional
    public List<Document> getUnassignedDocumentsForUser(User user) {

        Query query = entityManager.createQuery(new StringBuffer()
                .append("select d from Document d WHERE d.user = :user ")
                .append("AND NOT d IN( SELECT d from Book b, IN(b.docs) bd WHERE bd.id = d.id )")
                .append("").toString());
        query.setParameter("user", user);
        List<Document> tmp = (ArrayList<Document>) query.getResultList();
        for(Document doc : tmp){
            Hibernate.initialize(doc);
            Hibernate.initialize(doc.getUser());
            Hibernate.initialize(doc.getUser().getPerson());
            Hibernate.initialize(doc.getUser().getPerson().getLocation());
            entityManager.detach(doc);
            entityManager.detach(doc.getUser());
            entityManager.detach(doc.getUser().getPerson());
            entityManager.detach(doc.getUser().getPerson().getLocation());
        }
        return tmp;
    }

    @Transactional
    public Document save(Document doc) {

        if (doc.getId() == null) {
            entityManager.persist(doc);
            return doc;
        } else {
            return entityManager.merge(doc);
        }
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

}

ErrorExceptionResolver:

@Component
public class BWHandlerExceptionResolver extends SimpleMappingExceptionResolver implements InitializingBean{

    public void afterPropertiesSet() throws Exception {
        Properties props = new Properties();
        props.put(Exception.class.getName(),"error");
        this.setExceptionMappings(props);

    }

Upvotes: 3

Views: 7365

Answers (5)

Srikanth Malyala
Srikanth Malyala

Reputation: 959

I have faced the similar issue ,but got a solution to print the exceptions after writing code manually to parse the Object to JSON.

for example:

 public @ResponseBody String  getObject() {

  ObjectMapper mapper = new ObjectMapper();
  try {
          return mapper.writeValueAsString(object);

      } catch (IOException ex) {
          ex.printStackTrace();      
          }


   }

Upvotes: 0

Krešimir Nesek
Krešimir Nesek

Reputation: 5502

I experienced almost identical issue but wanted to know what exactly made Jackson to fail, instead of only getting 500 internal server with no exception stack trace anywhere in the logs. After inspecting Spring MVC's sources, first place where the exception is caught is in ServiceInvocableHandlerMethod.java (lines 109-116):

...
try {
    returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request);
} catch (Exception ex) {
    if (logger.isTraceEnabled()) {
        logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex);
    }
    throw ex;
}
...

handleReturnValue(...) throws the exception and it is caught here but not printed out because logger.isTraceEnabled() returns false in default configuration. In order to see the exception stack trace in the logs I needed to enable TRACE level logging on the aforementioned class. In my particular case (I use log4j) here is the relevant part of log4j.properties:

...
log4j.logger.org.springframework.web.servlet.mvc.method.annotation=TRACE

log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Threshold=TRACE
...

Upvotes: 6

Jigar Parekh
Jigar Parekh

Reputation: 6283

Setting up fetch type eager or marking @Transactional controller is not a good solution. Use jackson hibnerate module to solve this. This will not try to initilize the objects which are proxys.

Upvotes: 3

user1285221
user1285221

Reputation: 71

The problem is that jackson resolves/serializes your object after transaction is closed. One simple solution to this problem is to set all your relationships auf FetchType.EAGER.

I'm still looking for better solution :(

Upvotes: 1

Kyle Richter
Kyle Richter

Reputation: 406

The answer to this was to wrap the controller method in a transaction by giving it the @Transactional annotation. That fixed my issue.

Upvotes: 0

Related Questions