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>