測試自動生成的REST應用程序時出錯我使用NetBeans助手開發了一個簡單的RESTful應用程序用於測試目的。爲此,我跟着這個視頻:https://www.youtube.com/watch?v=RNJynlcqHNs。這個過程有點不同,因爲我使用的是最新版本的NetBeans,但它應該具有相同的結果。在簡歷我所使用的工具:使用NetBeans + Glassfish + JAX-RS + JPA
- 的NetBeans 8.1
- GlassFish服務器4.1.1
- MySQL的(沒有任何問題,到現在爲止)
的助手:
- 實體來自數據庫的類
- 來自實體類的RESTful服務
- REST風格的JavaScript客戶端(使用骨幹+ HTML5打造一個「功能齊全」的客戶端)
據我所知,這個項目包括的EclipseLink(JPA 2.1)和新澤西州。
該項目部署正確,我可以直接從瀏覽器訪問服務,但是當我嘗試使用自動生成的Javascript客戶端訪問服務時,它無法完全加載。 Additionaly,我得到這個錯誤在瀏覽器的控制檯:
無法加載資源:服務器500(內部服務器錯誤)的狀態
迴應,我得到了NetBeans/Glassfish的這個錯誤「控制檯」:
Advertencia: StandardWrapperValve[service.ApplicationConfig]: Servlet.service() for servlet service.ApplicationConfig threw exception
java.lang.NoClassDefFoundError: Could not initialize class org.eclipse.persistence.jaxb.BeanValidationHelper
at org.eclipse.persistence.jaxb.JAXBBeanValidator.isConstrainedObject(JAXBBeanValidator.java:257)
at org.eclipse.persistence.jaxb.JAXBBeanValidator.shouldValidate(JAXBBeanValidator.java:208)
etc...
這是JPA實體類代碼:
package model;
import java.io.Serializable;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
/**
*
* @author USER
*/
@Entity
@Table(name = "oficina")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "Oficina.findAll", query = "SELECT o FROM Oficina o"),
@NamedQuery(name = "Oficina.findByIdOficina", query = "SELECT o FROM Oficina o WHERE o.idOficina = :idOficina"),
@NamedQuery(name = "Oficina.findByNumero", query = "SELECT o FROM Oficina o WHERE o.numero = :numero"),
@NamedQuery(name = "Oficina.findByDescripcion", query = "SELECT o FROM Oficina o WHERE o.descripcion = :descripcion")})
public class Oficina implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "id_oficina")
private Integer idOficina;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 10)
@Column(name = "numero")
private String numero;
@Size(max = 100)
@Column(name = "descripcion")
private String descripcion;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idOficina")
private List<Investigador> investigadorList;
public Oficina() {
}
public Oficina(Integer idOficina) {
this.idOficina = idOficina;
}
public Oficina(Integer idOficina, String numero) {
this.idOficina = idOficina;
this.numero = numero;
}
public Integer getIdOficina() {
return idOficina;
}
public void setIdOficina(Integer idOficina) {
this.idOficina = idOficina;
}
public String getNumero() {
return numero;
}
public void setNumero(String numero) {
this.numero = numero;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
@XmlTransient
public List<Investigador> getInvestigadorList() {
return investigadorList;
}
public void setInvestigadorList(List<Investigador> investigadorList) {
this.investigadorList = investigadorList;
}
@Override
public int hashCode() {
int hash = 0;
hash += (idOficina != null ? idOficina.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 Oficina)) {
return false;
}
Oficina other = (Oficina) object;
if ((this.idOficina == null && other.idOficina != null) || (this.idOficina != null && !this.idOficina.equals(other.idOficina))) {
return false;
}
return true;
}
@Override
public String toString() {
return "model.Oficina[ idOficina=" + idOficina + " ]";
}
}
這是服務代碼:
package service;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import model.Oficina;
/**
*
* @author USER
*/
@Stateless
@Path("oficinas")
public class OficinaFacadeREST extends AbstractFacade<Oficina> {
@PersistenceContext(unitName = "grupoItosWSPU")
private EntityManager em;
public OficinaFacadeREST() {
super(Oficina.class);
}
@POST
@Override
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void create(Oficina entity) {
super.create(entity);
}
@PUT
@Path("{id}")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public void edit(@PathParam("id") Integer id, Oficina entity) {
super.edit(entity);
}
@DELETE
@Path("{id}")
public void remove(@PathParam("id") Integer id) {
super.remove(super.find(id));
}
@GET
@Path("{id}")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Oficina find(@PathParam("id") Integer id) {
return super.find(id);
}
@GET
@Override
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Oficina> findAll() {
return super.findAll();
}
@GET
@Path("{from}/{to}")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public List<Oficina> findRange(@PathParam("from") Integer from, @PathParam("to") Integer to) {
return super.findRange(new int[]{from, to});
}
@GET
@Path("count")
@Produces(MediaType.TEXT_PLAIN)
public String countREST() {
return String.valueOf(super.count());
}
@Override
protected EntityManager getEntityManager() {
return em;
}
}
最後,這是Javascript代碼:
var app = {
// Create this closure to contain the cached modules
module: function() {
// Internal module cache.
var modules = {};
// Create a new module reference scaffold or load an
// existing module.
return function (name) {
// If this module has already been created, return it.
if (modules[name]) {
return modules[name];
}
// Create a module and save it under this name
return modules[name] = {Views: {}};
};
}()
};
(function (models) {
// Model for Oficina entity
models.Oficina = Backbone.Model.extend({
urlRoot: "http://localhost:8080/grupoItosWS/api/oficinas/",
idAttribute: 'idOficina',
defaults: {
descripcion: "",
numero: ""
},
toViewJson: function() {
var result = this.toJSON(); // displayName property is used to render item in the list
result.displayName = this.get('idOficina');
return result;
},
isNew: function() {
// default isNew() method imlementation is
// based on the 'id' initialization which
// sometimes is required to be initialized.
// So isNew() is rediefined here
return this.notSynced;
},
sync: function (method, model, options) {
options || (options = {});
var errorHandler = {
error: function (jqXHR, textStatus, errorThrown) {
// TODO: put your error handling code here
// If you use the JS client from the different domain
// (f.e. locally) then Cross-origin resource sharing
// headers has to be set on the REST server side.
// Otherwise the JS client has to be copied into the
// some (f.e. the same) Web project on the same domain
alert('Unable to fulfil the request');
}
};
if (method === 'create') {
options.url = 'http://localhost:8080/grupoItosWS/api/oficinas/';
}
var result = Backbone.sync(method, model, _.extend(options, errorHandler));
return result;
}
});
// Collection class for Oficina entities
models.OficinaCollection = Backbone.Collection.extend({
model: models.Oficina,
url: "http://localhost:8080/grupoItosWS/api/oficinas/",
sync: function (method, model, options) {
options || (options = {});
var errorHandler = {
error: function (jqXHR, textStatus, errorThrown) {
// TODO: put your error handling code here
// If you use the JS client from the different domain
// (f.e. locally) then Cross-origin resource sharing
// headers has to be set on the REST server side.
// Otherwise the JS client has to be copied into the
// some (f.e. the same) Web project on the same domain
alert('Unable to fulfil the request');
}
};
var result = Backbone.sync(method, model, _.extend(options, errorHandler));
return result;
}
});
})(app.module("models"));
(function (views) {
views.ListView = Backbone.View.extend({
tagName: 'tbody',
initialize: function (options) {
this.options = options || {};
this.model.bind("reset", this.render, this);
var self = this;
this.model.bind("add", function (modelName) {
var row = new views.ListItemView({
model: modelName,
templateName: self.options.templateName
}).render().el;
$(self.el).append($(row));
$(self.el).parent().trigger('addRows', [$(row)]);
});
},
render: function (eventName) {
var self = this;
_.each(this.model.models, function (modelName) {
$(this.el).append(new views.ListItemView({
model: modelName,
templateName: self.options.templateName
}).render().el);
}, this);
return this;
}
});
views.ListItemView = Backbone.View.extend({
tagName: 'tr',
initialize: function (options) {
this.options = options || {};
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
template: function (json) {
/*
* templateName is element identifier in HTML
* $(this.options.templateName) is element access to the element
* using jQuery
*/
return _.template($(this.options.templateName).html())(json);
},
render: function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
close: function() {
var table = $(this.el).parent().parent();
table.trigger('disable.pager');
$(this.el).unbind();
$(this.el).remove();
table.trigger('enable.pager');
}
});
views.ModelView = Backbone.View.extend({
initialize: function (options) {
this.options = options || {};
this.model.bind("change", this.render, this);
},
render: function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
template: function (json) {
/*
* templateName is element identifier in HTML
* $(this.options.templateName) is element access to the element
* using jQuery
*/
return _.template($(this.options.templateName).html())(json);
},
/*
* Classes "save" and "delete" are used on the HTML controls to listen events.
* So it is supposed that HTML has controls with these classes.
*/
events: {
"change input": "change",
"click .save": "save",
"click .delete": "drop"
},
change: function (event) {
var target = event.target;
console.log('changing ' + target.id + ' from: ' + target.defaultValue + ' to: ' + target.value);
},
save: function() {
// TODO : put save code here
var hash = this.options.getHashObject();
this.model.set(hash);
if (this.model.isNew() && this.collection) {
var self = this;
this.collection.create(this.model, {
success: function() {
// see isNew() method implementation in the model
self.model.notSynced = false;
self.options.navigate(self.model.id);
}
});
} else {
this.model.save();
this.model.el.parent().parent().trigger("update");
}
return false;
},
drop: function() {
this.model.destroy({
success: function() {
/*
* TODO : put your code here
* f.e. alert("Model is successfully deleted");
*/
window.history.back();
}
});
return false;
},
close: function() {
$(this.el).unbind();
$(this.el).empty();
}
});
// This view is used to create new model element
views.CreateView = Backbone.View.extend({
initialize: function (options) {
this.options = options || {};
this.render();
},
render: function (eventName) {
$(this.el).html(this.template());
return this;
},
template: function (json) {
/*
* templateName is element identifier in HTML
* $(this.options.templateName) is element access to the element
* using jQuery
*/
return _.template($(this.options.templateName).html())(json);
},
/*
* Class "new" is used on the control to listen events.
* So it is supposed that HTML has a control with "new" class.
*/
events: {
"click .new": "create"
},
create: function (event) {
this.options.navigate();
return false;
}
});
})(app.module("views"));
$(function() {
var models = app.module("models");
var views = app.module("views");
var AppRouter = Backbone.Router.extend({
routes: {
'': 'list',
'new': 'create'
,
':id': 'details'
},
initialize: function() {
var self = this;
$('#create').html(new views.CreateView({
// tpl-create is template identifier for 'create' block
templateName: '#tpl-create',
navigate: function() {
self.navigate('new', true);
}
}).render().el);
},
list: function() {
this.collection = new models.OficinaCollection();
var self = this;
this.collection.fetch({
success: function() {
self.listView = new views.ListView({
model: self.collection,
// tpl-oficina-list-itemis template identifier for item
templateName: '#tpl-oficina-list-item'
});
$('#datatable').html(self.listView.render().el).append(_.template($('#thead').html())());
if (self.requestedId) {
self.details(self.requestedId);
}
var pagerOptions = {
// target the pager markup
container: $('.pager'),
// output string - default is '{page}/{totalPages}'; possiblevariables: {page}, {totalPages},{startRow}, {endRow} and {totalRows}
output: '{startRow} to {endRow} ({totalRows})',
// starting page of the pager (zero based index)
page: 0,
// Number of visible rows - default is 10
size: 10
};
$('#datatable').tablesorter({widthFixed: true,
widgets: ['zebra']}).
tablesorterPager(pagerOptions);
}
});
},
details: function (id) {
if (this.collection) {
this.oficina = this.collection.get(id);
if (this.view) {
this.view.close();
}
var self = this;
this.view = new views.ModelView({
model: this.oficina,
// tpl-oficina-details is template identifier for chosen model element
templateName: '#tpl-oficina-details',
getHashObject: function() {
return self.getData();
}
});
$('#details').html(this.view.render().el);
} else {
this.requestedId = id;
this.list();
}
},
create: function() {
if (this.view) {
this.view.close();
}
var self = this;
var dataModel = new models.Oficina();
// see isNew() method implementation in the model
dataModel.notSynced = true;
this.view = new views.ModelView({
model: dataModel,
collection: this.collection,
// tpl-oficina-details is a template identifier for chosen model element
templateName: '#tpl-oficina-details',
navigate: function (id) {
self.navigate(id, false);
},
getHashObject: function() {
return self.getData();
}
});
$('#details').html(this.view.render().el);
},
getData: function() {
return {
idOficina: $('#idOficina').val(),
descripcion: $('#descripcion').val(),
numero: $('#numero').val()
};
}
});
new AppRouter();
Backbone.history.start();
});
我一直在尋找了一段時間的解決方案,但我還沒有找到它,此外,由於我對JAX-RS或骨幹不太瞭解,所以對我來說這個錯誤是神祕的。有沒有人有過這個問題?有沒有解決方法?
非常感謝!當我試圖在netbeans和glassfish中做一些基本的REST API時,這讓我瘋狂。如果有人想知道,manifest.mf文件已經設置了換行符,但是我發現我可以直接將Ken的代碼示例粘貼到清單中,而不用擔心將換行匹配。 –
有人可以提供一些關於如何更新manifest.mf文件的指導嗎?每次我嘗試更新時,都會收到「未指定的錯誤」。我正在使用7-zip和記事本 –