Ninja Web Framework

Ninja Web Framework Tutorial – Part 2

This is a continuation of the two part series featuring the significance and use of Ninja Web Framework. In Part 1, we understood the motivations behind the making of Ninja framework and the different features it offers. In part 2, we will look at developing a sample application using Ninja API and framework semantics.

We will develop a simple login application that will have an input form which will accept and authenticate user credentials (user id and password). Upon successful authentication, we will display the name of the user in the next page. The application on paper looks very simple but we will cover important Ninja semantics that will help you understand the framework better and use it to build a more professional real world web project. A brief flow will be something like this: A user will navigate to the login page and enter its user id and password and proceed to click on the Login button. The system will perform basic validation and then authenticate the user id by checking the database to see if the id exists or not. If user exists, then the authentication is successful otherwise a decent error message is displayed. Once logged in, the user information in the session will be valid for 60 seconds during which time if you try to access the login page, it will indicate you that the user is already logged in.

Before, we deep dive into the development, let’s setup the application environment

1. Setting up application environment

Application environment setup is a very important step. We will use Maven to build and deploy the application. For database, we will use MySQL and setup ORM using JPA Hibernate implementation. Application related global settings will be defined in the application.conf file.

1.1 Maven setup

We will create a Maven project. If you are not used to creating Maven based projects from scratch then you can get the sample project shell using the Maven archetype ‘ninja-servlet-archetype-simple’. This will create the basic Ninja application for you. The folder hierarchy will look like the following:

Maven Folder Structure
Maven Folder Structure

As discussed in Part 1 of this article, Ninja is modeled on convention than configuration and therefore you need to make sure the folder hierarchy and its components are named according to the framework norms. The routes (Routes.java) and module (Module.java) will be part of conf/ folder. The application global settings will be defined in the application.conf file again as part of conf/ folder. The controllers folder will host the controller class, we will name it as ApplicationController.java. The views folder will contain views categorized as controller defined views, layouts and system error messages. We will manually add two new folders viz. filter and entity that will contain filter and entity class respectively. The below code shows the important portions of Maven POM file:

pom.xml

<project ...>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>myninja</artifactId>
	<packaging>war</packaging>
	<groupId>demo.ninja</groupId>
	<version>1.0</version>
	...
	<build>
		<plugins>
			...
			<plugin>
				<groupId>org.ninjaframework</groupId>
				<artifactId>ninja-maven-plugin</artifactId>
				<version>${ninja.version}</version>
			</plugin>
		</plugins>
		...
	</build>
	<dependencies>
		...
		<dependency>
			<groupId>org.ninjaframework</groupId>
			<artifactId>ninja-servlet</artifactId>
			<version>${ninja.version}</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.37</version>
		</dependency>

	</dependencies>
</project>

The ninja-maven plugin allows you to make use of SuperDev feature of Ninja. The feature is briefly described in Part 1 of this article. We will deploy the application as a WAR file and therefore you need to configure the dependency named ninja-servlet.

1.2 DB setup

We will setup our database as MySQL. We will create the DB or schema and name it as ‘ninja’. Then we will create a table named ‘user’ that will have the relevant columns and add a sample record that will contain a user id as ‘ninja’ and password as ‘password’. We will authenticate the user against this sample record. The below figure shows the table columns and the sample data.

User Table and Sample Data
User Table and Sample Data

1.3 Setting up ORM

We will use JPA-Hibernate to implement ORM for our sample login application. JPA makes use of persistence.xml file to define and configure the database as one unit of persistence. Our persistence.xml file will look like the following:

persistence.xml

<persistence ...>
	<persistence-unit name="mysql" transaction-type="RESOURCE_LOCAL">
	  <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
	 
	  <properties>
		<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
		<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
	 
		<property name="hibernate.show_sql" value="false" />
		<property name="hibernate.format_sql" value="false" /> 
	 
	  </properties>
	</persistence-unit>
</persistence>

As you can see, this file will have configuration details about MySQL like the driver class, dialect and other relevant properties. The file will be part of the META-INF/ folder. Next, we will configure the DB user credentials in the application.conf file:

application.conf

ninja.jpa.persistence_unit_name = mysql
db.connection.url=jdbc:mysql://localhost:3306/ninja
db.connection.username=sa
db.connection.password=

The above code snippet shows the ORM configuration defined in the application.conf file. The said file is placed under the conf/ folder. It makes a reference to the persistence unit that we defined in the persistence.xml file. The persistence unit here is named ‘mysql’.

As our table name is ‘user’, we will create an entity class named ‘User’ that will map this table. The following code shows the entity class:

User.java

@Entity
public class User {
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	Long id;
	
	@NotNull
	@Column(name = "USER_ID")
	String userId;
	
	@NotNull
	@Column(name = "PASSWORD")
	String password;
	...
	// Getter and setters

The entity class uses JPA based annotations to map it with the user table. The code is pretty self explanatory. Also note the use of @NotNull annotation, this is a Java validation feature employed on this entity class. It indicates that user id and password cannot be null and must have some value.

1.4 Setting up web.xml

Let’s take a brief look at the web.xml:

web.xml

<web-app ...>

    <display-name>ninja</display-name>

	<listener>
		<listener-class>ninja.servlet.NinjaServletListener</listener-class>
	</listener>

	<filter>
		<filter-name>guiceFilter</filter-name>
		<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>guiceFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

The class NinjaServletListener is used to bootstrap the Ninja application that tracks and bind dependencies in the application. The class GuiceFilter is used to perform Guice based dependency injection.

2. Creating View

Ninja uses Apache Freemarker to define template views. The code snippet below depicts a view template that renders our login page (login.ftl.html).

login.ftl.html

<#import "../layout/defaultLayout.ftl.html" as layout> <@layout.myLayout
"Login page">

<form action="/doLogin" method="post">
	<div>
		${i18n("label.user.id")}: <span><input type="text"
			name="userId" /></span>
	</div>
	<div> </div>
	<div>
		${i18n("label.password")}: <span><input type="text"
			name="password" /></span>
	</div>
	<div> </div>
	<div>
		<button type="submit">Login</button>
	</div>
</form>

</@layout.myLayout>

It defines a directive called @layout that contains the login form. This form will be replaced in the main layout file that is constructed using Freemarker tags like macro, nested etc. At this point, it is just enough for you to understand that we have designed a login page using some view template. There is also another view (result.ftl.html) that will show the appropriate message once the user logs in. Also observe the i18n object that is used to localize the view in the language of your choice. The locale specific labels are stored in the messages.properties file under conf/ folder. For simplicity, we will not deep dive into the semantics of Apache Freemarker as it is a seperate topic in itself. More information on Freemarker can be found at: http://freemarker.incubator.apache.org/docs/index.html

3. Defining Routes and Controller

With most of the traditional MVC frameworks, request routing and delegation happens within a controller itself. With Ninja, URL routing is defined in a seperate file called Routes.java and the action methods, to which the requests are delegated, are defined in the controller class. Routing is the process of making a HTTP request using a well defined URL with one of the four HTTP methods viz. GET, POST, PUT and DELETE. This request is then mapped with the action method defined in the controller. We will configure our routes as following:

Routes.java

public class Routes implements ApplicationRoutes {

    @Override
    public void init(Router router) {  
        router.GET().route("/").with(ApplicationController.class, "login");
        router.POST().route("/doLogin").with(ApplicationController.class, "performLogin");
		...

The root /URL will invoke the login() method that will render the login page. The /doLogin URL will invoke the performLogin() method that will display the welcome message. The methods login() and performLogin() are defined as part of ApplicationController class. Both the said methods returns the Result object which contains the destination view to be rendered.

ApplicationController.java

@Singleton
public class ApplicationController {

	@Inject
	Provider entitiyManagerProvider;

	@FilterWith(LoginFilter.class)
    public Result login() {
        return Results.html();

    }

    public Result performLogin(User user, Session session) {
		String userId = user.getUserId();
		if (userExists(userId)) {
			session.put("userId", userId);
			return Results.html().template("/views/ApplicationController/result.ftl.html").render("msg", "Welcome " + user.getUserId());
		} else {
			session.clear();
		}
		return Results.html().template("/views/ApplicationController/result.ftl.html").render("msg", "Invalid user, please login again ");
    }
    
	private boolean userExists(String userId, String password) {
		EntityManager entityManager = entitiyManagerProvider.get();
		List users = entityManager.createQuery("from User where userId = ?1 and password = ?2").setParameter(1, userId).setParameter(2, password).getResultList();
		return (users.size() > 0) ? true : false;
	}
}

The controller is injected with the EntityManager component that represents our database. The userExists() method will check against the database whether user id and password exists or not. It makes use of JPA based EntityManager class to perform the query against User entity. If the credentials are found then the user id is put in the session.

4. Defining Filter

We will define a nice little filter that will check the session if it contains the user id. If the sesssion is empty it means we have to show the user a login page. The filter class is LoginFilter and it looks like the following:

LoginFilter.java

public class LoginFilter implements Filter {
	public Result filter(FilterChain chain, Context context) {
		String userId = context.getSession().get("userId");
		if (userId != null) {
			return Results.html().template("/views/ApplicationController/result.ftl.html").render("msg", "You are logged in!");
		}
		
		return chain.next(context);
	}
}

It gets the user id from the session using context object and checks if it is null. If it is not found then take the user to the login page or display a message saying the user is already loggged in.

5. Build and Deploy

You can use Eclipse to import the codebase as a Maven project. Alternatively, you can use command shell to manually build the code. To manually build the code, you can give the following command:

Perform Build

mvn clean install

Please note that this command is to be given inside the folder where your Maven POM file resides. To run the application, give the following command:

Run the server in SuperDev mode

mvn ninja:run

The above command will run the Ninja in the SuperDev mode thereby provding the hot deploy feature.

6. Conclusion

As you saw it is so simple to build a web application with Ninja. Though we took a very simple use case, it covered important concepts of Ninja. We saw how the MVC pattern is implemented through the use of routes and conroller. For the view, Ninja uses Apache Freemarker. The view here is a template organized as layouts. You could make use of locale specific labels using the i18n object. We covered the session handling by persisting the user information in the session. Remember, Ninja handles session in the cookies and not on the server side. We looked at defining the filter that intercepts the request to check the login state. For persistence, we used JPA Hibernate as a ORM framework to query user data. There was also a use of Dependency Injection (DI) by way of @Inject annotation provided by Google Guice. There are still plently of features one can explore and this article has given you the base foundation for developing a more real world Ninja web application.

7. Download the Source Code

This was a tutorial showing the web application development using Ninja web framework.

Download
You can download the full source code of this tutorial here: Ninja Web Framework Tutorial

Rajeev Hathi

Rajeev is a senior Java architect and developer. He has been designing and developing business applications for various companies (both product and services). He is co-author of the book titled 'Apache CXF Web Service Development' and shares his technical knowledge through his blog platform techorgan.com
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button