En proceso
PUT /books/{bookId}/authors
Es el encargado de remplazar la colección de objetos author asociada a un objeto book. Esta colección corresponde a la relación muchos a muchos entre Book y Author
Parámetros
Nombre | Ubicación | Descripción | Requerido | Esquema |
---|---|---|---|---|
bookId | Path | ID del objeto book cuya colección será remplazada | Sí | Integer |
body | body | Colección de objetos author | Sí | Representación minimum |
Respuesta
Código | Descripción | Cuerpo |
---|---|---|
200 | Se remplazó la colección | Colección de objetos author en representación minimum |
500 | No se pudo remplazar la colección | Mensaje de error |
Llamada al servicio en AngularJS
Cuando se hace clic en Ok en el modal de Authors en la interfaz de Books, esta invoca la función close
del modal, la cual a su vez resuelve el promise
de modal.result
, invocando la implementación de dicha promesa. En este caso, se invoca al método replaceAuthors
de bookService
:
modal.result.then(function (data) {
bookSvc.replaceAuthors($scope.refId, data).then(function (response) {
$scope.records.splice(0, $scope.records.length);
$scope.records.push.apply($scope.records, response.data);
$scope.$emit("updateAuthors", $scope.records);
}, responseError);
});
Cuando el llamado a
replaceAuthors
obtiene una respuesta exitosa, la aplicación actualiza en el scope la lista de autores asociados y emite el eventoupdateAuthors
.
Envío de petición con $http
El servicio replaceAuthors
se encarga de hacer la petición al API REST con el método PUT
, enviando una colección de objetos author con el ID de los objetos que deben conformar la colección:
this.replaceAuthors = function (bookId, authors) {
return $http.put(context + "/" + bookId + "/authors", authors); // authors es la colección de objetos author con su respectivo ID
};
Este método retorna un promise para ser utlizado por cualquiera que lo invoque.
La variable
context
se define en una constante del módulobookModule
llamadabookContext
y define la URL base a la cual debe hacerse la petición. Esto se encuentra en el archivobook.mod.js
.
(function (ng) {
var mod = ng.module("bookModule", ["ui.bootstrap", "ngMessages"]);
mod.constant("bookContext", "api/books"); // Definición de bookContext
})(window.angular);
JAX-RS recibe la petición en un String con formato JSON
Una vez el front envía la petición, el servlet de JAX-RS la recibe. Basándose en la URL solicitada (/books/{bookId}/authors
), y en el método HTTP (PUT
), JAX-RS busca una clase con la anotación @Path('books')
y que tenga un método con la anotación @PUT
y la anotación @Path('{bookId: \\d+}/authors')
, el cual será ejecutado para responder a la solicitud.
@Path("books")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class BookResource {
// ...
@PUT
@Path("{bookId: \\d+}/authors")
public List<AuthorDTO> replaceAuthors(@PathParam("bookId") Long bookId, List<AuthorDTO> authors) {
// ...
}
//...
}
Sin embargo, se observa que la firma del método indica que recibe como parámetro una colección de BookDTO
, pero la petición trae un string. Es aquí cuando JAX-B hace su trabajo.
Dado que la clase AuthorDTO
está anotada con @XmlRootElement
, JAX-RS puede deserializar el JSON y mapear sus datos con los de AuthorDTO
usando JAX-B. Para esto, JAX-B busca en AuthorDTO
los accesores (getters y setters) cuyo nombre corresponda a los atributos en el JSON. Por ejemplo, para la propiedad name
buscaría un método setName
. Así, JAX-B deserializa el JSON, crea una lista de AuthorDTO
y la entrega al método para su ejecución.
Conversión del DTO a Entity
Ya tenemos en el recurso un DTO con los datos. Sin embargo, el API de la lógica recibe objetos Entity, para lo cual es necesario realizar la conversión entre estas dos estructuras. Para esto, el converter ofrece un conjunto de métodos, con nombres compuestos de dos partes que indican el objetivo de cada uno:
La primer parte del nombre, y su significad, puede ser alguno de los siguientes
- ref: Métodos que se usan cuando se transmitirá información como una referencia desde otro, los cuales usualmente son
id
yname
. Por ejemplo, cuando se carga una instancia de Book y se quiere saber el nombre de la Editorial a la que está asociada, se usa este método para convertir la editorial. De esta manera, se evita la transmisión de datos innecesarios. - basic: Métodos que se usan dentro de los métodos list. Estos realizan la conversión de todos los atributos de la entidad a excepción de las colecciones. Esto con el fin de evitar consultas exhaustivas que contengan toda la base de datos. Además que al listar registros de una entidad no se visualiza sus colecciones.
- full: Métodos que se usan cuando se consulta una instancia específica de una entidad. Estos convierten todos los atributos de una entidad incluyendo las colecciones composite. Se usas cuando se desea editar una instancia.
- list: Métodos usados para convertir listas de registros. Estos por dentro usan los métodos basic.
- child: Métodos para convertir instancias de DTO a Entity que son hijas de una relación composite, para la cual asignan la instancia del padre que le corresponde.
La segunda parte del nombre indica el sentido de la conversión:
- Entity2DTO: Realiza la conversión de instancias de Entity a DTO
- DTO2Entity: Realiza la conversión de instancias de DTO a Entity
En el caso del PUT
se usa el método listDTO2Entity
(el cual por dentro invoca a basicDTO2Entity
), que permite realizar la conversión de todos los atributos de book:
private static AuthorEntity basicDTO2Entity(AuthorDTO dto) {
if (dto != null) {
AuthorEntity entity = new AuthorEntity();
entity.setId(dto.getId());
entity.setName(dto.getName());
entity.setBirthDate(dto.getBirthDate());
return entity;
} else {
return null;
}
}
public static List<AuthorEntity> listDTO2Entity(List<AuthorDTO> dtos) {
List<AuthorEntity> entities = new ArrayList<AuthorEntity>();
if (dtos != null) {
for (AuthorDTO dto : dtos) {
entities.add(basicDTO2Entity(dto));
}
}
return entities;
}
Una vez el converter haga su trabajo, se tendrá disponible la lista de AuthorEntity para invocar al servicio del API de la lógica:
Invocación de API de lógica
Para la invocación de la lógica, se inyectó una instancia de IBookLogic
, cuya inicialización es resuelta por el contenedor:
@Inject
private IBookLogic bookLogic; // Inyección de instancia de IBookLogic
@PUT
@Path("{bookId: \\d+}/authors")
public List<AuthorDTO> replaceAuthors(@PathParam("bookId") Long bookId, List<AuthorDTO> authors) {
List<AuthorEntity> authorList = AuthorConverter.listDTO2Entity(authors);
List<AuthorEntity> newAuthors = bookLogic.replaceAuthors(authorList, bookId);
return AuthorConverter.listEntity2DTO(newAuthors);
}
La implementación de este método busca el objeto book con el id de bookId
y le asigna la lista de autores recibida. Finalmente retorna dicha lista:
@Inject
private BookPersistence persistence;
@Override
public List<AuthorEntity> replaceAuthors(List<AuthorEntity> authors, Long bookId) {
BookEntity bookEntity = persistence.find(bookId);
bookEntity.setAuthors(authors);
return bookEntity.getAuthors();
}
Retorno de la respuesta
Una vez la persistencia retorna la entidad modificada, la capa lógica la retorna esta misma entidad hasta el servicio replaceAuthors
sin modificarla.
Cuando el servicio recibe la respuesta, necesita serializarla en formato JSON para poder transmitirla con el protocolo HTTP. Aquí es donde JAX-B vuelve a entrar en acción. Al igual que JAX-B puede deserializar los DTO, también tiene la capacidad de serializarlo, mapeando los atributos del objeto DTO a propiedades del objeto JSON. Así, cada método accesor (getter) es mapeado a una propiedad de la instancia. En este caso, book quedaría como un objeto JSON, donde cada propiedad accesible con un get, está disponible como atributo del JSON.
En el front-end, $http
recibe la respuesta y la pone a disposición del promise como argumento. De esta manera, quien invoque al servicio (en este caso el controlador) puede usar la respuesta para lo que desee.
modal.result.then(function (data) {
bookSvc.replaceAuthors($scope.refId, data).then(function (response) { // response es la respuesta del servidor
$scope.records.splice(0, $scope.records.length);
$scope.records.push.apply($scope.records, response.data);
$scope.$emit("updateAuthors", $scope.records);
}, responseError);
});