テスト目的で、NetBeans アシスタントを使用して単純な RESTful アプリケーションを開発しました。このために、次のビデオをフォローしました: https://www.youtube.com/watch?v=RNJynlcqHNs . 最新バージョンの NetBeans を使用しているため、プロセスは少し異なりましたが、同じ結果になるはずでした。履歴書では、次のツールを使用しました。
- NetBeans 8.1
- Glassfish サーバー 4.1.1
- MySQL (今のところ問題なし)
アシスタント:
- データベースからのエンティティ クラス
- エンティティ クラスからの RESTful サービス
- RESTful Javascript クライアント (Backbone+HTML5 を使用して「完全に機能する」クライアントを構築する)
私の知る限り、このプロジェクトには EclipseLink (JPA 2.1) と Jersey が含まれています。
プロジェクトは正しくデプロイされ、ブラウザからサービスに直接アクセスできますが、自動生成された Javascript クライアントを使用してサービスにアクセスしようとすると、サービスが完全にロードされません。さらに、ブラウザのコンソールに次のエラーが表示されます。
リソースのロードに失敗しました: サーバーは 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 や Backbone についてあまり知らないので、エラーは不可解です。誰かが以前にこの問題を抱えていましたか? 解決策はありますか?