guilhebl guilhebl - 4 months ago 25
JSON Question

JAXB on JAX-RS - JPA Entity with relations returns no JSON HTTP 500 no error on logs (Glassfish)

I am trying to build a REST endpoint using JAX-RS to return JPA entities in JSON format.
I found a similar question
but even though after applying all similar changes in my case I still get HTTP 500 Internal Error code and Glassfish produces no log or shows no error messages related with this request.

Here is the code:

Entity class:

@XmlRootElement
@Entity
@Table(name = "TB_BANNER_IMAGE")
public class BannerImage extends BaseEntity<Integer> {

private FileReference fileReference;
private String type;
private String labelTitle;
private String labelText;

public BannerImage() {}

@Id
@TableGenerator(name="genBannerImage", table="TB_ID_GENERATOR",
pkColumnName="ID_NAME", valueColumnName="ID_VAL",
pkColumnValue="TB_BANNER_IMAGE", allocationSize=1)
@GeneratedValue(strategy=GenerationType.TABLE, generator="genBannerImage")
@Column(name = "ID_BANNER_IMAGE", unique = true, nullable = false)
public Integer getId() {
return super.getId();
}

@Override
public void setId(Integer id) {
super.setId(id);
}

@Column(name="TYPE")
public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="ID_FILE_REFERENCE", nullable=false)
public FileReference getFileReference() {
return fileReference;
}

public void setFileReference(FileReference fileReference) {
this.fileReference = fileReference;
}

@Column(name="LABEL_TITLE")
public String getLabelTitle() {
return labelTitle;
}

public void setLabelTitle(String labelTitle) {
this.labelTitle = labelTitle;
}

@Column(name="LABEL_TEXT")
public String getLabelText() {
return labelText;
}

public void setLabelText(String labelText) {
this.labelText = labelText;
}

}


and

@XmlRootElement
@Entity
@Table(name = "TB_FILE_REFERENCE")
public class FileReference extends BaseNamedEntity<String> {

private String type;

public FileReference() {}

@Id
@TableGenerator(name="genFileReference", table="TB_ID_GENERATOR",
pkColumnName="ID_NAME", valueColumnName="ID_VAL",
pkColumnValue="TB_FILE_REFERENCE", allocationSize=1)
@GeneratedValue(strategy=GenerationType.TABLE, generator="genFileReference")
@Column(name = "ID_FILE_REFERENCE", unique = true, nullable = false)
public String getId() {
return super.getId();
}
@Override
public void setId(String id) {
super.setId(id);
}

@Column(name = "TYPE")
public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

}


Base Generic Superclass:

@MappedSuperclass
public abstract class BaseNamedEntity<ID extends Serializable> implements INamedEntity<ID>, Comparable, Serializable {

private ID id;
private String name;

protected BaseNamedEntity() {}

public String getName() {
return name;
}

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

@Transient
public ID getId() {
return id;
}

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

@Override
public boolean equals(Object obj) {
if (obj != null && obj instanceof BaseNamedEntity) {
BaseNamedEntity base2 = (BaseNamedEntity) obj;
if (this.getId() != null && base2.getId() != null) {
return this.getId().equals(base2.getId());
}
}
return false;
}

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

@Override
public String toString() {
return name;
}

@Override
public int compareTo(Object arg0) {
if (this == arg0) {
return 0;
}
BaseNamedEntity<ID> other = (BaseNamedEntity<ID>) arg0;
return getName().compareTo(other.getName());
}
}


Application JAX-RS configuration:

@ApplicationPath("/rest")
public class PortalApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<Class<?>>();
// register root resource
classes.add(BannerImageService.class);
return classes;
}
}


Service class:

@Path("/banner")
public class BannerImageService extends BaseServiceFacade<BannerImage, Integer> {
public BannerImageService() {
super(BannerImage.class);
}

@Override
protected boolean validateEntity(BannerImage entity) {
if (entity != null
&& entity.getId() != null
&& entity.getFileReference() != null
&& entity.getFileReference().getName() != null
&& RegexUtil.getInstance().validateFileName(
entity.getFileReference().getName())) {
return true;
}

return false;
}

@Override
protected String getDefaultQuery() {
return null;
}

@Override
protected boolean validateID(Integer id) {
return RegexUtil.getInstance().validateIntegerID(id);
}

@Override
public Crud<BannerImage, Integer> lookupService() throws ServiceLocatorException {
return ServiceLocator.getInstance()
.getLocalHome(ServicesConstants.BANNER_IMAGE_SERVICE);
}
}


and

public abstract class BaseServiceFacade<T extends IEntity<ID>, ID extends Serializable> implements ServiceFacadeRest<T, ID> {

protected static final Logger log = Logger.getLogger("BaseServiceFacade");
protected Crud<T, ID> service;
protected Class<T> clazz;

public BaseServiceFacade(Class<T> classe) {
clazz = classe;
}

protected abstract boolean validateEntity(T entity);
protected abstract boolean validateID(ID id);
protected abstract String getDefaultQuery();
protected abstract Crud<T, ID> lookupService() throws ServiceLocatorException;

public Crud<T,ID> getService() {
try {
if (service == null) {
service = lookupService();
}

}catch (Exception ex) {
logException(ex);
}
return service;
}

public void setService(Crud<T,ID> service) {
this.service = service;
}

public Class<T> getClazz() {
return clazz;
}

public void serviceException(ServiceException ex) {
log.log(Level.INFO, ex.getMessage());
}

public void logException(Exception ex) {
log.log(Level.INFO, ex.getMessage());
ex.printStackTrace();
}

@Override
@GET
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Path("/query")
public List<T> query() {
try {
String defaultQuery = getDefaultQuery();
if (defaultQuery != null) {
return getService().search(defaultQuery);
} else {
return getService().findAll(clazz);
}

} catch (ServiceException e) {
serviceException(e);
} catch (Exception ex) {
logException(ex);
}
return null;
}

@Override
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Path("/get/{id}")
public T get(@PathParam("id") ID id) {
try {
if (validateID(id)) {
return getService().findById(clazz, id);
}

} catch (ServiceException e) {
serviceException(e);
} catch (Exception ex) {
logException(ex);
}
return null;
}

@Override
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Path("/create")
public T create(T entity) {
try {
if (validateEntity(entity)) {
getService().insert(entity);
return entity;
}

} catch (ServiceException e) {
serviceException(e);
} catch (Exception ex) {
logException(ex);
}
return null;
}

@Override
@PUT
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Path("/update/{id}")
public T update(T entity) {
try {
if (validateEntity(entity)) {
return getService().update(entity);
}
} catch (ServiceException e) {
serviceException(e);
} catch (Exception ex) {
logException(ex);
}
return null;
}

@Override
@DELETE
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Path("/delete/{id}")
public boolean delete(@PathParam("id") ID id) {
try {
if (validateID(id)) {
return getService().delete(clazz, id);
}

} catch (ServiceException e) {
serviceException(e);
} catch (Exception ex) {
logException(ex);
}
return false;
}

}


When I hit localhost/app/rest/banner/query

I get a HTTP 500 internal Error code page and Glassfish returns an empty HTML
with :
"The server encountered an internal error that prevented it from fulfilling this request."

When I try to search the log files I see no errors, and the calls are being made through the service layers and back:

[2014-03-19T20:39:28.898-0300] [glassfish 4.0] [FINE] [] [org.eclipse.persistence.session.file[tid: _ThreadID=20 _ThreadName=http-listener-1(1)] [timeMillis: 1395272368898] [levelValue: 500] [[
SELECT ID_BANNER_IMAGE, LABEL_TEXT, LABEL_TITLE, TYPE, ID_FILE_REFERENCE FROM TB_BANNER_IMAGE]]


even though the logs show no errors and the calls being made, from the UI I can't actually see what's the error source, only a default HTTP 500 Internal Error page.

After commenting out the @ManyToOne JPA Entity Relation of FileReference class I could get a HTTP 200 and JSON output like:

[
{
"@type":"bannerImage",
"id":1,
"type":"main"
},
{
"@type":"bannerImage",
"id":2,
"type":"main"
},
...


in the Entity relation
@ManyToOne(fetch=FetchType.LAZY)

if I switch to
@ManyToOne(fetch=FetchType.EAGER)


then I get a JSON response so I guess the issue is related to the FileReference instance being null during the original request.

[
{
"@type":"bannerImage",
"id":1,
"fileReference":{
"id":"2bdbb063d0d0ee2939c89763945d9d9e",
"name":"banner1.png",
"type":"image/png"
},
"type":"main"
},
{
"@type":"bannerImage",
"id":2,
"fileReference":{
"id":"b33fa2041f2989f58a25dca2a6a35025",
"name":"banner2.png",
"type":"image/png"
},
"type":"main"
},


but there is a @type attribute which is not on my model and is created alongside which I can't determine precisely from where it's coming from, perhaps it's due to the response type of the "query" method being a generic type
List<T>

Answer

The issue happened to be related to the EntityMapping FetchType.LAZY after refactoring I could get a correct JSON response after including:

on BannerImageService.java:

@Override
protected String getDefaultQuery() {
    return BannerImageDAO.GET_ALL_FETCH_FILE_REF;
}

and in BannerImageDAO:

public static final String GET_ALL_FETCH_FILE_REF = "SELECT a FROM BannerImage a join fetch a.fileReference";

after manually fetching the relation the JSON response was correct. The only strange point is that Glassfish didn't log any exceptions or any related info related to the HTTP 500 Internal Error, but I guess this is another topic.