Examples (iOS)
Examples (Android)

 

» Server-side Extensions

SmartFoxServer 2X Extensions have been revisited and improved under many aspects with respect to SmartFoxServer Pro. We have concentrated our attention on the Java Extension development and have dropped support for scripting languages. The main reasons are:

The Java Extensions framework has been vastly improved. The server-side API was written to comply with the latest Java 6 standards, taking advantage of all the latest features such as concurrent collections, generics, enums, annotations, etc. The API also conforms with Java best practices and provide an unprecedented level of customization.

The development cycle has been simplified in order to allow one-click deployment from your IDE and complicated operations such as classpath modifications or class loading matters are completely managed behind the scenes.

» One Extension to rule everything

The first noteworthy change in the Extensions architecture is that developers can plug a single Extension to their Room or Zone. This is in contrast with SmartFoxServer Pro, where it was possible to attach multiple Extensions. The rationale behind this is that the new Extensions will allow you to do more with less:

» Extension overview

Java Extensions are deployed in a single .jar file to a folder which represents the name of the Extension. The image below shows the main extensions/ folder which is the root under which all Extensions are published. Our testExtension folder contains one jar file with all our code.

Extension Folder

There are a few more things to notice:

Both approaches can be useful depending on what you need to do:

» Custom configuration

Each Extension can auto-load a .properties file containing custom settings that are immediately available in your code. By default the Extension API will attempt to load a file called config.properties, but you will be able to specify any other file name. This is particularly useful when attaching the same Extension to multiple Zones or Rooms but you need to pass different settings to each one.

This is an example of how an Extension is configured in the AdminTool (see the Zone Extension tab or Room Extension tab in the Zone Configurator module documentation for additional informations and important notes):

Extension Setup

Where:

» Classpath management

The good news is that you don't have to touch the classpath as in SmartFoxServer Pro. SmartFoxServer 2X scans the dependency and Extension folders and loads all the jar files for you.

» Extension reloading

SmartFoxServer 2X provides Extensions hot-redeploy which can be very useful during the development phases. When this feature is turned on (see the Custom configuration paragraph above), the server will monitor your Extension folders and reload your code when a jar file is modified.
All you need to do is configure your Java IDE to build or copy the jar file directly under the Extension folder in your SmartFoxServer path, and you will have a one-click deploy system.

» Required dependencies

In order to start creating your own Extensions you will need to add a few libraries to the project in your favorite IDE:

The logging jar(s) are not mandatory, unless you need specific logging features:

» Server-side javadoc

You can consult the server-side API javadoc. The main entry point of the API is the com.smartfoxserver.v2.api.SFSApi class. Please note that the usage of undocumented methods and properties may cause the system malfunctioning.

» A peek at the Extension API

There are two main classes that provide the base for any Extension you will create:

» The simplest Extension possible

Let's take a look at the most simple Extension class we can possibly write:

 
public class MyFirstExtension extends SFSExtension
{
	@Override
	public void init()
	{
		trace("Hello, this is my first SFS2X Extension!");
	}
}

This is the bare minimum to create a fully functional Extension: one method, init.
Of course you might be already thinking that you will not be able to accomplish much with a single method, so let's add a request handler. We want the user to be able to send us two numbers and we will add them and send the sum back. A full video tutorial showing how to create this simple Extension is also available in our YouTube channel.

The recommended practice when developing an Extension is that each request or event handler is a separate class in order to clearly separate each piece of logic in your code. There are two interfaces we provide to create a Request Handler or a Server Event Handler. Before we create our addition request handler we have to define which parameters we expect from the client: in this simple example we expect two integers called n1 and n2 and we will send back an integer that is the sum of the two.

Now we can start coding the handler:

 
public class AddReqHandler extends BaseClientRequestHandler
{
	@Override
	public void handleClientRequest(User sender, ISFSObject params)
	{
		// Get the client parameters
		int n1 = params.getInt("n1");
		int n2 = params.getInt("n2");
		
		// Create a response object
		ISFSObject resObj = SFSObject.newInstance(); 
		resObj.putInt("res", n1 + n2);
		
		// Send it back
		send("add", resObj, sender);
	}
}

The handleClientRequest method receives the following two parameters:

The code in the above example should be self-explanatory. We first obtain the two expected integers, prepare a new SFSObject for the response and add the result n1 + n2. Finally we invoke the send method on the parent Extension to send the response back to the user. The following parameters are passed to the method:

In order to "connect" this handler with the main Extension all we need to do is adding one line in the init method of the Extension:

 
@Override
public void init()
{
	trace("Hello, this is my first SFS2X Extension!");
	
	// Add a new Request Handler
	addRequestHandler("add", AddReqHandler.class)
}	

The addRequestHandler method allows to register an handler for a specific request id. Similarly it is possible to add any numbers of server event handlers using the addEventHandler method. This is an example of how you can listen to the USER_LOGIN server event:

 
public class LoginEventHandler extends BaseServerEventHandler
{
	@Override
	public void handleServerEvent(ISFSEvent event) throws SFSException
	{
		String name = (String) event.getParameter(SFSEventParam.LOGIN_NAME);
		
		if (name.equals("Gonzo") || name.equals("Kermit"))
			throw new SFSLoginException("Gonzo and Kermit are not allowed in this Zone!");
	}
}
Just like before, we have now to go back to the main Extension class to modify the init method:
 
@Override
public void init()
{
	trace("Hello, this is my first SFS2X Extension!");
	
	// Add a new Request Handler
	addRequestHandler("add", AddReqHandler.class)
	
	// Add a new SFSEvent Handler
	addEventHandler(SFSEventType.USER_LOGIN, LoginEventHandler.class);
}	

Now that we have created our first basic Extension, there are few things that we can note:

If you need to customize the destroy method behaviour you can simply override it. Of course do not forget to call super.destroy to make sure that events/request handlers are auto-unregistered.

 
@Override
public void destroy()
{
	super.destroy()
	
	/*
	* More code here...
	*/
}	

In conclusion it is also worth mentioning some useful methods that are inherited by any command or server handlers:

» Advanced Extension features

Now that we have covered the basics of the new Extesion 2.0 architecture we can delve into the more advanced features that allow more sophisticated control over your code.

» Instantiation annotations

We provide several useful annotations that can be used to specify how your handler classes should be instantiated. By default when you declare a request/event handler class this will be instantiated as new on every call. You can change this behavior using the @Instantiation annotation on your classes:

» Multi handlers and the request dot-syntax

In order to properly organize request names in complex application we have established a convention similar to Java package naming that uses a "dot syntax". Suppose your Extension can handle a number of games and other operations such as user registration and profile editing. You can organize all these requests like this:

register.submitForm
register.passwordLost
register.changeEmail
register. ...
register. ...

profile.changeAvatarType
profile.changeNick
profile.getAvatar
profile. ...
profile. ...

checkers.sendMove
checkers.getMyScore
checkers.leaveGame
checkers. ...
checkers. ...
The Extension API provides a @MultiHandler annotation that can be added to your handler class definition. This will register the class for all requests starting with a certain prefix. Let's implement a multi-handler for the register set of requests:

 
@MultiHandler
public class RegisterMultiHandler extends BaseClientRequestHandler
{
	@Override
	public void handleClientRequest(User sender, ISFSObject params)
	{
		// Obtain the request custom name
		String command = params.getUtfString(SFSExtension.MULTIHANDLER_REQUEST_ID);
        
        if (command.equals("submitForm"))
        	handleSubmitForm(sender, params);
        else if (command.equals("changeEmail"))
        	handleChangeEmail(sender, params);
        
        // ... etc ...
	}
    
    private void handleSubmitForm(User sender, ISFSObject params)
    {
    	// ... app logic here
    }
    
    private void handleChangeEmail(User sender, ISFSObject params)
    {
    	// ... app logic here
    }
}

In the multi-handler, the specific request id among the "register" ones of the example above is obtained from the parameters object passed to the handler. An if or switch statement can be used then to execute the proper code depending on the request id.

Now we register the class in the init method of the Extension as we learnt before.

 
@Override
public void init()
{
	trace("Hello, this is my first multi handler test!");
	
	// Add a new Request Handler
	addRequestHandler("register", RegisterMultiHandler.class)
}	

The only real difference in this example is that the handler class is marked as @MultiHandler. When this is done, the Extension dispatcher invokes the handler on any request id starting with the "register" prefix. In other words it will handle register.*.

NOTE
You can also mix the @Instantiation annotation with the @MultiHandler one.

In conclusion it is also worth to notice that you are not limited to a single "dot" in the request name. You could have multiple nested levels such as: games.spacewars.fireBullet or user.profile.avatar.getHairColor, etc. The only recommendation we have is to keep these request names reasonably short since they will be transmitted with the request/response objects.

» Extension Filters

The last advanced feature of this tour is Extension Filters. If you are familiar with the Java Servlet API this will probably ring a bell. Extension Filters in SmartFoxServer are inspired by servlet filters and they serve a similar purpose: they are executed in a chain and they can be used to log, filter, or handle specific requests or events before they get to the Extension itself.

The advantage of pluggable Filters is that they don't get in the way of your Extension code, their execution order can be altered and they can even stop the execution flow if necessary. An example of this could be a custom ban filter where user credentials are checked against a black list before the request is passed to your Login Handler.

This is an example of a simple Extension filter:

 
public class CustomFilter extends SFSExtensionFilter
{
	@Override
	public void init(SFSExtension ext)
	{
	    super.init(ext);
	    trace("Filter inited!");
	}
 
	@Override
	public void destroy()
	{
		trace("Filter destroyed!");
	}
 
	@Override
	public FilterAction handleClientRequest(String cmd, User sender, ISFSObject params)
	{
		// If something goes wrong you can stop the execution chain here!
		if (cmd.equals("BadRequest"))
	    	return FilterAction.HALT;
		else
	    	return FilterAction.CONTINUE;
	}
 
	@Override
	public FilterAction handleServerEvent(ISFSEvent event)
	{
		return FilterAction.CONTINUE;
	}
}

Filters can be easily added to any Extension at configuration time or dynamically, at runtime:

 
@Override
public void init()
{
	/*
	* This is your Extension main class init()
	*/
 
	// Add filters
	addFilter("customLoginFilter", new CustomLoginFilter());
	addFilter("pubMessageFilter", new PubMessageFilter());
	addFilter("privMessageFilter", new PrivMessageFilter());
}

When a new request or event is sent to your Extension it will first traverse the Filters Chain in the order in which Filters were added. In the example above it will be: customLoginFilter » pubMessageFilter » privMessageFilter » Extension.