In this article we’re going to build a simple Login Extension using EclipseLink in place of the default DBManager API provided by SFS2X.
EclipseLink is a powerful ORM (object-relational mapping) framework, that is compliant with the Java Persistence API (JPA) standard.
NOTE: the tutorial presupposes you have an understanding of the basic concepts of object-relational mapping and you’re familiar with at least one database technology. If you don’t have experience with these tools we recommend to check this introduction.
For this tutorial we have downloaded the latest EclipseLink version from the website (v 2.6 at the time of writing) and created a new project in our IDE. The first step is to create a lib/ folder in the project and copy the necessary dependencies. In this case we need to extract two jar files from the EclipseLink distribution as show here:
Then add the two jars to the Eclipse’s build path (or equivalent if you’re working with a different iDE). Right click on the project node, select Properties and click Add Jars… Select the two libraries and finally click OK.
Now we’re ready to create our model for the User class. Let’s go ahead and create the UserCredential class:
package sfs2x.demo.eclipselink.model; @Entity @Table(name="user_credentials") public class UserCredential { @Id @Column(name = "user_id") @GeneratedValue(strategy=GenerationType.SEQUENCE) private Long id; @Basic @Column(name = "user_name", nullable = false, length = 40) private String name; @Basic @Column(name = "pwd", nullable = false, length = 40) private String password; @Basic @Column(name = "email", nullable = false, length = 50) private String email; @Basic @Column(name = "ctry", nullable = false, length = 2) private String countryCode; @Column(name = "role", length = 25) @Enumerated(EnumType.STRING) private UserRole role; @Temporal(TemporalType.DATE) @Column(name = "creation_date") private Date creationDate; // Getters and setters // ... // ... }
The code defines our basic user model, with JPA annotations providing extra metadata for the database, e.g. the field name, length of the field and if it can be null. We also reference another class called UserRole (an enumerated type) that defines the user role. Here is the code:
package sfs2x.demo.eclipselink.model; public enum UserRole { GUEST, REGISTERED, MODERATOR, ADMIN; }
With the basic model in place we can now write the SFS2X Extension code.
package sfs2x.demo.eclipselink; public class ELDemoExtension extends SFSExtension { private static final String PERSISTENCE_UNIT_NAME = "ELDemoExtension"; private EntityManagerFactory emf; private EntityManager em; @Override public void init() { emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME); em = emf.createEntityManager(); initDatabase(); // Activates the login routing to the Extension without manual configuration getParentZone().setCustomLogin(true); // Add event handler for login events addEventHandler(SFSEventType.USER_LOGIN, new LoginEventHandler(em)); trace("EclipseLink Demo Extension ready."); } /* * If the DB doesn't contain any record create one, for testing */ private void initDatabase() { Query query = em.createQuery("SELECT uc FROM UserCredential uc"); List<UserCredential> res = (List<UserCredential>) query.getResultList(); // Table is empty, populate 1 record if (res.size() == 0) { UserCredential uc = new UserCredential(); uc.setName("user"); uc.setPassword("password"); uc.setCountryCode("ES"); uc.setEmail("user@email.es"); uc.setRole(UserRole.GUEST); uc.setCreationDate(new Date()); // Store em.getTransaction().begin(); em.persist(uc); em.getTransaction().commit(); } } }
The code above does three important things:
- Creates the EntityManagerFactory: this allow to manage our persistence units which can be one or multiple ones. In our example we will just access one database resource. The persistence unit provides all the necessary settings to work with different storage resources and it is defined inside the persistence.xml which we’re going to discuss in a moment
- Create the EntityManager which is our persistence context, managing the various objects in our data model. In this case the UserCredential and UserRole classes.
- Initialize the database: for the sake of this demo when the DB is created the first time we’ll populate it with one UserCredential instance so that we can test it later from the client side.
Finally the code adds a listener for the USER_LOGIN server event. In order to start testing the what we have done so far, we can add an empty listener for now. We will add the authentication code after having tested that the database connection and EclipseLink configuration work.
This is the empty Login listener class. Notice that in the constructor we’re passing the EntityManager instance built in the main Extension class.
package sfs2x.demo.eclipselink; public class LoginEventHandler extends BaseServerEventHandler { private final EntityManager em; public LoginEventHandler(EntityManager em) { this.em = em; } @Override public void handleServerEvent(ISFSEvent event) throws SFSException { } }
» Deployment
1) Setting up the database and driver
We will assume you have a database setup locally that you can use for testing. In our case we have worked with MySQL 5 and have proceeded to download the JDBC MySQL driver from their website and installed it under {SFS2X}/extensions/__lib__/
2) Setting up the persistence.xml file
The persistence.xml file contains all the settings that EclipseLink uses to connect to the database. This how it looks like:
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <!-- RDBMS Persistence Unity, MySQL ............................................. --> <persistence-unit name="ELDemoExtension" transaction-type="RESOURCE_LOCAL"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <class>sfs2x.demo.eclipselink.model.UserCredential</class> <class>sfs2x.demo.eclipselink.model.UserRole</class> <properties> <property name="javax.persistence.jdbc.password" value="root"/> <property name="javax.persistence.jdbc.user" value="root"/> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/ELDemoExtension"/> <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/> <property name="eclipselink.logging.level" value="INFO"/> </properties> </persistence-unit> </persistence>
The file needs to be found under the META-INF/persistence.xml path. We can create the folder and the related file directly under the main SFS2X directory, which is seen as the relative root once SFS2X is started.
Make sure to change the database connection parameters according to your local configuration.
3) Deploy the dependencies
Make sure to copy the two dependency jar files from the IDE’s lib/ folder to {SFS2X}/extensions/__lib__/
4) Deploy the Extension
The best way to deploy our extension is to split the actual server side classes from the model classes and export the latter in the top {SFS2X}/extensions/__lib__/ folder. This will avoid class loading issues, especially across multiple reloads of the Extension.
In order to do this we can create two separate export scripts in Eclipse (and the same can be done if you’re using NetBeans or IntellyJ) that will automate the work for us.
By right-clicking on the project we select Export…
We then proceed by selecting only the model package (thus excluding the Extension classes), choose a destination for the jar file (e.g. {SFS2X}/extension/__lib__/) and exporting the jar file.
We will then repeat the same procedure for the Extension classes, package them as a single jar file and save it as under the {SFS2X}/extensions/EclipseLinkDemo.
5) Configure SmartFoxServer
Last but not least we’ll open the SFS2X Admin Tool, launch the Zone Configurator and select the extension.
Time to restart SmartFox and check what we have done so far.
» After the startup
If all goes well you should see the Extension being loaded correctly in the log files and you should find the new database table created and populated with the default record.
The next step is to complete our USER_LOGIN event handler:
package sfs2x.demo.eclipselink; public class LoginEventHandler extends BaseServerEventHandler { private final EntityManager em; public LoginEventHandler(EntityManager em) { this.em = em; } @Override public void handleServerEvent(ISFSEvent event) throws SFSException { SFSErrorData errData = null; ISession session = (ISession) event.getParameter(SFSEventParam.SESSION); String userName = (String) event.getParameter(SFSEventParam.LOGIN_NAME); String password = (String) event.getParameter(SFSEventParam.LOGIN_PASSWORD); // Query the DB List<UserCredential> res = em.createQuery("SELECT uc FROM UserCredential uc WHERE uc.name = ?1", UserCredential.class). setParameter(1, userName). getResultList(); if (res.size() > 0) { UserCredential uc = res.get(0); // Password is verified, return without throwing exceptions if(getApi().checkSecurePassword(session, uc.getPassword(), password)) return; // Create the error code that will be sent to the client and raise the exception errData = new SFSErrorData(SFSErrorCode.LOGIN_BAD_PASSWORD); errData.addParameter(userName); } else { // User was not found, send error back errData = new SFSErrorData(SFSErrorCode.LOGIN_BAD_USERNAME); errData.addParameter(userName); } throw new SFSLoginException("LoginError", errData); } }
Using the JPA query language we lookup in the database a UserCredential instance whose name matches the name provided by the client. If they match we will get back at least one result. We then proceed by checking the encrypted password using the SmartFox checkSecurePassword method.
Finally it’s time redeploy the new Extension and test with a client.
» Testing with a client
The last step is to login into the system, which can be done with any of the examples we provide for the supported platforms, or we can write a simple client to run locally and test.
This is an example written in C# that will test our Eclipse-Link based login system:
namespace Connector { class SFS2XConnector { private SmartFox sfs; ConfigData cfg; public SFS2XConnector() { InitSFS2X(); } private void InitSFS2X() { sfs = new SmartFox(); sfs.ThreadSafeMode = false; sfs.AddEventListener(SFSEvent.CONNECTION, OnConnection); sfs.AddEventListener(SFSEvent.CONNECTION_LOST, OnConnectionLost); sfs.AddEventListener(SFSEvent.LOGIN, OnLogin); sfs.AddEventListener(SFSEvent.LOGIN_ERROR, OnLoginError); cfg = new ConfigData(); cfg.Host = "localhost"; cfg.Port = 9933; cfg.Debug = true; cfg.Zone = "BasicExamples"; sfs.Connect(cfg); Trace("Now connecting..."); } private void OnConnection(BaseEvent evt) { bool success = (bool) evt.Params ["success"]; if (success) { Trace ("Connected: " + sfs.ConnectionMode); sfs.Send (new LoginRequest ("username", "password", cfg.Zone)); } else { Trace ("Connection FAILED"); } } private void OnConnectionLost(BaseEvent evt) { Trace("Connection was Lost, Reason: " + evt.Params["reason"]); } private void OnLogin(BaseEvent evt) { Trace("Success! Logged in as: " + sfs.MySelf.Name); } private void OnLoginError(BaseEvent evt) { Trace("LOGIN ERROR " + evt.Params["errorMessage"]); } static void Main(string[] args) { new SFS2XConnector(); Console.ReadKey (); } private static void Trace(string msg) { Console.WriteLine(msg); } } }
» Download sources
You can download all the java files from via this link: EclipseLinkDemo