See: Description
Package | Description |
---|---|
ch.codebulb.crudlet.config | |
ch.codebulb.crudlet.model | |
ch.codebulb.crudlet.model.errors | |
ch.codebulb.crudlet.service | |
ch.codebulb.crudlet.util | |
ch.codebulb.crudlet.webservice |
A simple, lean JAX-RS based framework to build CRUD REST-to-SQL web applications running on a Java web application server, e.g. as an AngularJS backend.
From the project's GitHub repository's documentation:
Use JitPack to add its dependency to your Maven web application project:
<dependency>
<groupId>com.github.codebulb</groupId>
<artifactId>crudlet</artifactId>
<version>0.1</version>
</dependency>
...
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
Replace the version by the tag / commit hash of your choice or -SNAPSHOT
to get the newest SNAPSHOT.
Visit JitPack’s docs for more information.
@Entity
model and get production-ready REST CRUD operations in a few lines of code.Note: The complete source code of an example application (server and client) is available in a separate GitHub repository.
You need to setup the JAX-RS Application servlet in the web.xml file as shown in the demo project:
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Define your database connection in the persistence.xml file. Any JDBC compliant connection is supported. In the demo project, we use a JTA data source the configuration of which is set up in the application server.
Crudlet by default allows you to handle CORS request without nasty errors as is usually desired in development / debug stage. The required request / response filters are implemented in the CorsRequestFilter
and CorsResponseFilter
class, respectively.
Set the CorsRequestFilter#ALLOW_OPTIONS
and CorsResponseFilter#ALLOW_CORS
boolean flag to false (e.g. in a @Startup
@Singleton
EJB bean) to disable CORS allow-all policy.
Crudlet provides a simple, lean framework to build REST-to-SQL web applications based on common best practices. Having a basic CRUD implementation in place means that you can an any entity type:
Building your application around a CRUD centric approach brings a couple of advantages:
This best practices architecture is based on three central artifacts for which Crudlet provides an abstract generic base implementation:
CrudEntity
: the entity modelCrudService
: the persistence serviceCrudResource
: the REST web service endpointIn a CRUD application, the relation between these artifacts is 1 : 1 : 1; you will thus build a service and a controller for every entity. Thanks to the level of abstraction provided by Crudlet, this is a matter of about 30 lines of code:
CrudEntity
makes sure your entity implements an auto-ID generation strategyCrudService
implements basic persistence storage access (through an EntityManager) for the four CRUD operationsCrudResource
implements a REST web service endpoint for editing all entities in the persistence storage including out-of-the-box support for returning I18N-ready model validation error messages.Use either the CrudIdentifiable
interface or the CrudEntity
class to derive your entity model classes from. This is the only prerequisite to use them with a CrudService
and a CrudResource
.
The difference between the interface and the class is that the latter provides an auto-generated Long id field implementation out-of-the-box.
For instance, to create a Customer
entity:
@Entity
public class Customer extends CrudEntity {
@NotNull
@Pattern(regexp = "[A-Za-z ]*")
private String name;
private String address;
private String city;
...
Use Bean Validation constraints to declaratively specify the model validation.
In order to create a CRUD service for an entity type, implement CrudService
for the entity and register it as a CDI bean in the container (depending on beans.xml bean-discovery-mode, explicit registration may not be necessary).
For instance, to create the service for the Customer
entity:
public class CustomerService extends CrudService<Customer> {
@Override
@PersistenceContext
protected void setEm(EntityManager em) {
super.setEm(em);
}
@Override
public Customer create() {
return new Customer();
}
@Override
public Class<Customer> getModelClass() {
return Customer.class;
}
}
setEm(EntityManager)
method, simply call the super method. The important part is that you inject your @PersistenceContext
in this method by annotation.Of course, you are free to add additional methods to your CrudService
implementation where reasonable.
Finally, create the REST web service endpoint by implementing CrudResource
for the entity and register it as a @Stateless
EJB bean in the container.
For instance, to create the web service endpoint for the Customer
entity:
@Path("customers")
@Stateless
public class CustomerResource extends CrudResource<Customer> {
@Inject
private CustomerService service;
@Override
protected CrudService<Customer> getService() {
return service;
}
}
@Path
defines the base path of the web service endpoint.getService()
method, return the concrete CrudService
for the entity type in question which you should dependency-inject into the controller.That’s it. Now you can use e.g. the httpie command line tool to verify that you can execute RESTful CRUD operations on your entity running on the database.
Of course, you are free to add additional methods to your CrudResource
implementation where reasonable.
Read on for an example client implementation based on AngularJS.
In this example, we use Restangular as an abstraction layer to do RESTful HTTP requests which offers a far more sophisticated although more concise API than AngularJS’s built-in $http
and $resource
. It is set up as shown in the demo application’s main JavaScript file:
.config(function (RestangularProvider) {
RestangularProvider.setBaseUrl('http://localhost:8080/CrudletDemo.server/');
RestangularProvider.setRequestInterceptor(function(elem, operation) {
// prevent "400 - bad request" error on DELETE
// as in https://github.com/mgonto/restangular/issues/78#issuecomment-18687759
if (operation === "remove") {
return undefined;
}
return elem;
});
})
You also potentially want to install and setup the angular-translate module for I18N support:
.config(['$translateProvider', function ($translateProvider) {
$translateProvider.translations('en', translations);
$translateProvider.preferredLanguage('en');
$translateProvider.useMissingTranslationHandlerLog();
$translateProvider.useSanitizeValueStrategy('sanitize');
}])
In the “controller” JavaScript file, we can use Restangular to access the RESTful web service endpoint of our Crudlet Customer service like so:
Restangular.all("customers").getList().then(function(entities) {...})
Restangular.one("customers", $routeParams.id).get().then(function (entity) {...})
$scope.entity.save().then(function() {...})
An interesting aspect of Crudlet is its out-of-the-box support for localized validation error messages. If upon save, a validation error occurs, the server answers e.g. like this:
{
"validationErrors": {
"name": {
"attributes": {
"flags": "[Ljavax.validation.constraints.Pattern$Flag;@1f414540",
"regexp": "[A-Za-z ]*"
},
"constraintClassName": "javax.validation.constraints.Pattern",
"invalidValue": "Name not allowed!!",
"messageTemplate": "javax.validation.constraints.Pattern.message"
}
}
}
Using the angular-translate module of AngularJS we set up previously, we can show all localized validation messages like so:
<div class="alert alert-danger" ng-show="validationErrors != null">
<ul>
<li ng-repeat="(component, error) in validationErrors">
{{'payment.' + component | translate}}: {{'error.' + error.messageTemplate | translate:error.attributes }}
</li>
</ul>
</div>
The validationErrors.<property>.messageTemplate
part is the message template returned by the bean validation constraint. We can thus e.g. base the validation error localization on Hibernate’s own validation messages:
var translations = {
...
'error.javax.validation.constraints.Pattern.message': 'must match "{{regexp}}"',
...
};
(I preceded it with error.
here.)
Because the error object returned by the server is a map, we can also use it to conditionally render special error styling, e.g. using Bootstrap’s error style class:
ng-class="{'has-error': errors.amount != null}"
Similar to validation errors, some runtime exceptions will also return a user-friendly error response message. For instance, let’s assume that a Customer has a list of Payments and you try to delete a Customer with a non-empty Payments list:
{
"error": {
"detailMessage": "DELETE on table 'CUSTOMER' caused a violation of foreign key constraint 'PAYMENTCUSTOMER_ID' for key (1). The statement has been rolled back.",
"exception": "java.sql.SQLIntegrityConstraintViolationException"
}
}
Again, you can catch and display these in the AngularJS view:
<div class="alert alert-danger" ng-show="errorNotFound != null || error != null">
<ul>
<li ng-show="error != null">
{{'error.' + error.exception | translate}}
</li>
</ul>
</div>
With appropriate localization:
var translations = {
...
'error.java.sql.SQLIntegrityConstraintViolationException': 'Cannot delete an object which is still referenced to by other objects.',
...
};
Because in a real-world production environment, exposing details of an exception may be a security issue, you can suppress this user-friendly exception detail output by setting the RestfulExceptionMapper#RETURN_EXCEPTION_BODY
boolean flag to false.
For a complete example, please take a look at the example application. It also shows you how to easily implement a CrudResource
for nested resources.
If you want to lean more about building RESTful web applications based on vanilla JAX-RS and Restangular, you may enjoy a blog post I’ve written about it; it features a complete example application as well.
Crudlet maps these HTTP requests to persistence storage operations:
GET /contextPath/model
: service#findAll()
GET /contextPath/model/:id
: service#findById(id)
PUT /contextPath/model/
with entity or PUT /contextPath/model/:id
with entity or POST /contextPath/model/
with entity or POST /contextPath/model/:id
with entity: service#save(entity)
DELETE /contextPath/model/:id
or DELETE /contextPath/model/:id
with entity: service#delete(id)
You can e.g. use a @Startup
@Singleton
EJB bean to manipulate the following values on application startup to configure application behavior:
CorsRequestFilter#ALLOW_OPTIONS
: Disable the allow-all "preflight" CORS request filter.CorsResponseFilter#ALLOW_CORS
: Disable the allow-all CORS response filter.RestfulExceptionMapper#RETURN_EXCEPTION_BODY
: Disable user-friendly exception output.Crudlet is currently experimental. I’d like to make some stability updates before releasing a proper 1.0 version. It may still already be useful for evaluation purposes, or as a skeleton to build your own solution.
This is a private project I’ve started for my own pleasure and usage and to learn more about building (Ajax) REST APIs, and I have no plans for (commercial) support.
You can also find more information about this project on its accompanying blog post and in the API docs.
Copyright © 2016, codebulb.ch. All rights reserved.