User-defined Interface Development

Please refer to the internal class files of $XPUSH_HOME/lib/xpush-2.8.x.jar and $XPUSH_HOME/lib/nimbus-1.1.9.jar to develop a user-defined interface.

Authenticator Development

X-PUSH requires authentication for three external service connections.

Each service responsible for external connection requests actually requests authentication from external authenticators. The developer must develop this class directly, because the authentication methods and logic are different for each system.

The class responsible for authentication must implement the com.nexacro.xpush.fw.service.auth.Authenticator interface, and the class must be specified in the configuration file. If authentication is required, the corresponding class is loaded, and then authentication is requested.

The set class is generated into a jar file and placed in $XPUSH_HOME/lib. The installed jar must contain all the resources used by the implemented authenticator.

All Authenticators of the deployment is set as com.nexacro.xpush.fw.service.auth.DummyAuthenticator as the default. Other authenticator classes that can be used are as follows.

Class Name

Description

DummyAuthenticator

All users are allowed to access

UserPropertiesAuthenticator

Users registered in $XPUSH_HOME/conf/user.properties file are allowed

UserPropertiesEncryptAuthenticator

Users with encrypted values registered in $XPUSH_HOME/conf/user.properties file are allowed

UserPropertiesAuthenticator authenticates using the /conf/user.properties file. Users not registered in user.properties will fail to log in after generating AuthenticateException.

User setting method is "User ID"="User PASSWORD".

UserPropertiesEncryptAuthenticator class can perform user authentication by entering an encrypted password in $XPUSH_HOME/conf/user.properties.

Please refer to the user.properties password encryption for more information on password encryption.

Authenticator interface

The com.nexacro.xpush.fw.service.auth.Authenticator interface provided by X-PUSH declares the authenticate() method for authentication requests and the logout() method for notifying the existing system when a user logs out.

Authenticator.authenticate()

public UserProfile authenticate (String userId, String password) throws AuthenticateException, AuthenticateSystemException

Authentication is handled with the three transmitted strings. AuthenticateException is thrown if the authentication itself is unsuccessful, and AuthenticateSystemException is thrown for system errors such as database connection failures. If authentication is successful, the com.nexacro.xpush.fw.service.auth.UserProfile is returned as an inherited class. An X-PUSH Server does not change the returned UserProfile and delivers it when logout is called by the Message Formatter and authenticator. If additional information is needed to process the Message Formatter or logouts, the class that inherited the UserProfile is defined and returned.

Parameters

projectId

User projectId

userId

User ID

password

User password

Return value

UserProfile

Class that inherited the UserProfile class.

Exception

AuthenticateException

Occurs when authentication fails.

AuthenticateSystemException

Triggers when an error or failure occurs in the authentication system.

Authenticator.logout()

public void logout (UserProfile userProfile) throws AuthenticateSystemException

If a client breaks a connection, the X-PUSH Server calls the logout method by setting the UserProfile as a parameter. An X-PUSH Server only calls logout() to notify the client's connection termination to an external system. Therefore, the execution result of logout() does not affect the operation of the X-PUSH Server.

Parameters

UserProfile

Class containing user information.

Exception

AuthenticateSystemException

Triggers when an error or failure occurs in the authentication system.

Example

The DummyAuthenticator is a class with only the authenticator interface implemented. The authenticate() and logout() methods do not process any logic with respect to transmitted parameters. As a result, all inputs are handled as being authenticated.

import com.nexacro.xpush.fw.service.auth.AuthenticateException;
import com.nexacro.xpush.fw.service.auth.AuthenticateSystemException;
import com.nexacro.xpush.fw.service.auth.Authenticator;
import com.nexacro.xpush.fw.service.auth.UserProfile;


public class DummyAuthenticator implements Authenticator {

	public UserProfile authenticate(String projectID, String userID, String userPW) throws AuthenticateException, AuthenticateSystemException {
		UserProfile userProfile = null;
		userProfile = new UserProfile(projectID, userID, userPW);
		System.out.println("projectID : " + projectID);
		System.out.println("userID : " + userID);
		System.out.println("userPW : " + userPW);
		
		/*
		 * Success
		 */
		return userProfile; 

		/*
		 * Fail
		 */
//		throw new AuthenticateException("[Authentication] : FAIL");	
	}
		

	public void logout(UserProfile arg0) throws AuthenticateSystemException {
		System.out.println("logout function");
	}

}

Authenticator Installation

Place authenticator class that implemented the authenticator interface and other resources (other classes, property files) that require authenticators into the "$XPUSH_HOME/lib/classes" directory. Or, package them in a jar file, and place it into the "$XPUSH_HOME/lib" directory. When starting X-PUSH, all jar files located in "$XPUSH_HOME/lib" are included in the class path to be used.

Authenticator Setup

The MiPlatformReliabilityNettyWithProjectIDProtocol, SocketProviderProtocol, and MonitorProtocol services load the configured classes and request authentication when required. The authentication classes developed for this purpose are set in the X-PUSH configuration file "$XPUSH_HOME/conf/xpush_config.xml"

The developed authenticator class settings are as follows:

MiPlatformProtocolReliabilityAuthenticator service setup

<service name="MiPlatformProtocolReliabilityAuthenticator" ...>
	<attribute name="AuthenticatorClassName">
		com.nexacro.xpush.fw.service.auth.UserPropertiesReliabilityAuthenticator
	</attribute>
</service>

SocketProviderProtocolAuthenticator service setup

<service name="SocketProviderProtocolAuthenticator" ...>
	<attribute name="AuthenticatorClassName">
		com.nexacro.xpush.fw.service.auth.UserProfileDummyAuthenticator
	</attribute>
</service>

MonitorProtocol service setup

<service name="MonitorProtocol" ...>
	<attribute name="AuthenticatorServiceName">#MonitorProtocolAuthenticator</attribute>
		<depends>
			<service name="MonitorProtocolAuthenticator" code="com.nexacro.xpush.service.auth.AuthenticatorService">
				<attribute name="AuthenticatorClassName">
			com.nexacro.xpush.fw.service.auth.UserPropertiesEncryptAuthenticator
				</attribute>
			</service>
	</depends>
	<depends>Log</depends>
</service>

UserPropertiesAuthenticator authenticates using /conf/user.properties. Password is encrypted and users not registered in user.properties will fail to log in after generating AuthenticateException.

The set class is generated into a jar file and placed in $XPUSH_HOME/lib. The installed jar must contain all the resources used by the implemented authenticator.

All Authenticators of the deployment is set as com.nexacro.xpush.fw.service.auth.DummyAuthenticator as the default. Other authenticator classes that can be used are as follows.

Class Name

Description

DummyAuthenticator

All users are allowed to access

UserPropertiesRealiabilityAuthenticator

All users are allowed to access

UserPropertiesAuthenticator

Users registered in $XPUSH_HOME/conf/user.properties file are allowed

UserPropertiesEncryptAuthenticator

Users with encrypted values registered in $XPUSH_HOME/conf/user.properties file are allowed

UserPropertiesAuthenticator authenticates using the /conf/user.properties file. Users not registered in user.properties will fail to log in after generating AuthenticateException.

User setting method is "User ID"="User PASSWORD".

UserPropertiesEncryptAuthenticator class can perform user authentication by entering an encrypted password in $XPUSH_HOME/conf/user.properties.

Please refer to the user.properties password encryption for more information on password encryption.

Embedded Message Provider Development

The aforementioned Message Provider must be developed as an application to operate independently of an X-PUSH Server. Using the Embedded Message Provider, however, messages can be provided directly within an X-PUSH Server. As an X-PUSH Server is a Java implementation, the Embedded Message Provider must be developed in Java as well.

To use the Embedded Message Provider feature, the Actor class that actually provides messages must be developed, and it must be configured in "xpush_config.xml."

After starting, an X-PUSH Server loads the configured Actor class and calls the start() method. In addition, the stop() method is called at the time of shutdown.

Actor Class

Implement a class that inherits the "com.nexacro.xpush.api.EmbeddedMessageProviderActor" abstract class.

The methods to be implemented by the inherited class are start() and stop().

void start()

Void start() is called once at the time of X-PUSH Server startup. The start() method must be returned immediately after processing message provision. Otherwise, the X-PUSH Server will prevent further processing. Therefore, the thread for providing messages must be created in the start () method and must be returned after startup.

An X-PUSH Server will not operate if the start() method is blocked.

void stop()

Void stop() is called once at the time of X-PUSH Server shutdown.

Example

The following sample code pushes the current time in a message once every second. The example shows operating by creating a thread from the start() method and using the providerMessage(PushMessage) method to provide messages.

Import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.nexacro.xpush.api.EmbeddedMessageProviderActor;
import com.nexacro.xpush.fw.service.log.LogLevel;
import com.nexacro.xpush.fw.service.provider.PushMessage;

public class UserMessageProviderActor extends EmbeddedMessageProviderActor {
	Thread thread = null;
	public void start() {
		thread = new MessageGeneratorThread();
		thread.start();
	}

	public void stop() {
		((MessageGeneratorThread)thread).stopRunning();
	}

	public class MessageGeneratorThread extends Thread {
		private boolean isRunning = true;
		public void stopRunning() {
			isRunning = false;
		}

		public void run() {
			while(isRunning) {
				try {
					PushMessage pushMessage = new PushMessage();
					pushMessage.setEncoding("utf-8");
					SimpleDateFormat formatter = 
						new SimpleDateFormat("HH:mm:ss");
					String currentTime = "PUSHED CURRENT TIME : "
						+ formatter.format(new Date());
					pushMessage.setActionType(
						Constants.ACTION_PUSH_STRING);// Action Type
					pushMessage.setProjectID("PRO#1"); // Project ID
					pushMessage.setTopicType("OPDT ");// Topic Type
					pushMessage.setTopicId ("ALL ");// Topic Id
					pushMessage.addData("ALL");// data - value
					pushMessage.addData(currentTime);// data - value

					logger.write(LogLevel.DEBUG, "message provided");
					provideMessage(pushMessage);
					sleep(1000);
				} catch (InterruptedException e) {
					logger.write(LogLevel.ERROR, 
						"embedded message provider : exception occured.", e);
				} catch (UnsupportedEncodingException e) {
					logger.write(LogLevel.ERROR, 
						"embedded message provider : exception occured.", e);
				}
			}
		}
	}
}

Log Processor

There may be times when a log has to be directly received and processed. The LogDispatcherService is just the service for delivering the log directly.

The procedure for handling the service is as follows:

  1. Declare the class that implements the interface LogProcessor.

public class LogProcessorSample implements LogProcessor {
  1. Register itself in the LogDispatcherService at a constructor or in a suitable location.

LogDispatcherService.register(this);
  1. Implement the defined processLog() method in the interface LogProcessor.

public void processLog(String log) {
LogEvent event = LogParser.ParseLogIntoLogEvent(log);
System.out.println(event);
}

In the following example, the log is received from the Embedded Message Provider and output directly to the console. The UserMessageProviderActor from the example above was modified, and only the code for the LogProcessor was extracted.

import com.nexacro.xpush.api.EmbeddedMessageProviderActor;
import com.nexacro.xpush.service.logDispatch.LogProcessor
import com.nexacro.xpush.service.logDispatch.LogDispatcherService

public class LogProcessorSample extends EmbeddedMessageProviderActor 
	implements LogProcessor {

	public void start() {
		...
		LogDispatcherService.register(this);
	}

	public void processLog(String log) {
		System.out.println(log);
	}
}

Installation

Place the class that inherited the EmbeddedMessageProviderActor and other required resources (other classes, property files) into the "$XPUSH_HOME/lib/classes" directory. Or, package them in a jar file, and place it into the "$XPUSH_HOME/lib" directory. When starting X-PUSH, all jar files located in "$XPUSH_HOME/lib" are included in the class path to be used.

Setup

Set the class that inherited the EmbeddedMessageProviderActor in the EmbeddedMessageProviderActorClassName attribute of the EmbeddedPushMessageProvider service in "$XPUSH_HOME/conf/xpush_config.xml"

<service name="EmbeddedPushMessageProvider" ...>
	<attribute name="QueueServiceNames”>#PublisherPushMessageQueue</attribute>
	<attribute 	name="EmbeddedMessageProviderActorClassName">
		EmbeddedPushMessageProviderActorSampleWithProjectID
	</attribute>
...
</service>

Even with EmbeddedMessageProvider that does not do anything, the preparation is operated if it is set up. It should not be set and left blank when not in use.

Message Formatter Development

The Message Formatter can be used to send different messages to each client or to filter messages per client. For example, when names of clients such as "Mr./Mrs. XXX..." need to be included in messages, the message provider does not transmit multiple messages with each individual client's name. Rather, the name portion of a message is substituted with the corresponding client name before a message is transmitted.

An X-PUSH Server calls the Message Formatter's format () method just before transmitting a message to a client. At this time, a client's UserProfile, message type, message ID, and message contents are included a list (byte[] format) and transmitted. The transmitted UserProfile is an object returned by the authenticator's authenticate() method. The message type and ID are provided by the message provider, and the list contains the delivered bit stream in a byte[] format. The byte[] values contained in a list are values passed from the message provider API to the pushMessage.addData(). If two addData() values were called, the size of the list is 2 (List.size()), and there will be two byte[] values.

If a value returned by the Message Formatter's format() method is null, an X-PUSH Server does not push the respective message to the client. By taking advantage of this method, a filtering function can be implemented that determines whether messages are transmitted via a UserProfile's property value. By using appropriate logic, you can also implement a group messaging function that pools multiple messages to be transmitted at once.

Message Formatter Interface

Define a class that implements the "com.nexacro.xpush.fw.service.publish.MessageFormatter" interface.

X-PUSH server calls the format() method of MessageFormatter set right before sending a message to each client. MessageFormatter can convert the content of the message using the UserProfile passed as a parameter or filter the message according to the UserProfile.

MessageFormatter.format()

public List<byte[]> format(UserProfile userProfile, final String topicType, final String topicId, List<byte[]> valueBytesList)

The UserProfile returned as a result of authentication as well as the message topicType, topicId, and contents are contained in a list in the byte[] format, and they are called as parameters by an X-PUSH Server just before transmitting a message to a client. The byte[] contained in a returned list is actually delivered to the client. If a returned value is null, the respective message is not pushed to the client.

Parameters

userProfile

Object returned via the authenticator's authenticate() method.

topicType

Message type

topicId

Message key

valueBytesLists

List containing message values in byte[] formats.

Return value

List<byte[]>

List containing formatted values in byte[] formats.

Example

In the following example, NameMessagFormatter.java decodes the values transmitted in byte[] formats to UTF-8, and then substitutes "userName" values with those from userProfile.getUserName(). Substituted strings are re-encoded into UTF-8.

$r_title(NameMessageFormatter.java)
NameMessageFormatter.javaimport java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.nexacro.xpush.fw.service.auth.UserProfile;
import com.nexacro.xpush.fw.service.publish.MessageFormatter;

public class NameMessageFormatter implements MessageFormatter {
	public List<byte[]> format(UserProfile userProfile, 
		final String topicType, final String topicId, List<byte[]> valueBytesList) {

		List<byte[]> formattedValueBytesList = new ArrayList<byte[]>();
		String userName = (MyUserProfile(userProfile)).getUserName();
		for(int i=0; i<valueBytesList.size(); i++) {
			byte[] valueBytes = valueBytesList.get(i);
			byte[] formattedValueBytes = new byte[0];
			try {
				String value = new String(valueBytes, "utf-8");
				String formattedValue 
					= value.replace("userName", userName);
				formattedValueBytes = formattedValue.getBytes("utf-8");
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			formattedValueBytesList.add(formattedValueBytes);
		}
		return formattedValueBytesList;
	}
}
$r_title(MyUserProfile.java)
import com.nexacro.xpush.fw.service.auth.UserProfile;

public class MyUserProfile extends UserProfile {
	private String userName;

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getUserName() {
		return userName;
	}
}

In the following example, the DummyMessageFormatter does not perform any conversions. The DummyMessageFormatter is set to a default value at the time of deployment.

$r_title(DummyMessageFormatter.java)
import java.util.List;

import com.nexacro.xpush.fw.service.auth.UserProfile;
import com.nexacro.xpush.fw.service.publish.MessageFormatter;

public class DummyMessageFormatter implements MessageFormatter {
	public List<byte[]> format(UserProfile userProfile, final String topicType, 
		final String topicId, List<byte[]> valueBytesList) {
		return valueBytesList;
	}
}

Installation

Place the class that implemented the Message Formatter and other required resources (other classes, property files) into the "$XPUSH_HOME/lib/classes" directory. Or, package them in a jar file, and place it into the "$XPUSH_HOME/lib" directory. When starting X-PUSH, all jar files located in "$XPUSH_HOME/lib" are included in the class path to be used.

Setup

Set the class defined under the MessageFormatterClassName attribute in the MessageFormatterFactory service in the "xpush_config.xml" configuration file. An X-PUSH Server dynamically loads classes.

<service name="PublishContainerFactory">
	...
	<attribute name="MessageFormatterClassName">
		MyMessageFormatter
	</attribute>
	...
</service>

The Message Formatter is used whenever messages are sent to a client. Inadequate internal logic in the Message Formatter can have a direct impact on the performance of an X-PUSH Server.

If the Message Formatter is set up, preparatory work must be completed regardless of whether it's being used. If not being used, do not apply the settings and leave the item blank.

Received Message Processor Development

ReceivedMessageProcessor interface

Define the class that implements the "com.nexacro.xpush.api.ReceivedMessageProcessor" interface.

X-PUSH server calls the receive() method of ReceivedMessageProcessor after receiving a message from each provider. Records of sent messages can be logged or other follow-up actions can be processed using ReceivedMessageProcessor.

ReceivedMessageProcessor.receive()

public void receive(PushMessage pushmessage)

The function is called just before a message received from the provider is inserted into the main queue.

A push message that is to be transmitted when called contains the message topicType, topicId, and contents in a list in the byte[] format. This is the same format as a push message that is transmitted from a provider.

Parameters

pushmessage

Push message received by X-PUSH from a provider.

Example

In the following example, ReceivedMessageProcessorSampleClass.java outputs the contents of a received PushMessage to the screen.

$r_title(ReceivedMessageProcessorSample.java)
import com.nexacro.xpush.api.ReceivedMessageProcessor;
import com.nexacro.xpush.fw.service.provider.PushMessage;

public class ReceivedMessageProcessorSample implements ReceivedMessageProcessor {

	public void receive(PushMessage receivedMessage) {
	try 
	{
		File file = new File("./ReceivedMessageList.txt");
		FileWriter fileWriter = new FileWriter(file, true);

		StringBuffer sb = new StringBuffer()
			.append(" Topic Type: ")
			.append(receivedMessage.getTopicType())
			.append(" Topic ID: ").append(receivedMessage.getTopicId());

		for (byte[] value : receivedMessage.getValueBytesList()) {
			sb.append(" Value: ").append(new String(value));
		}

		fileWriter.write(sb.toString());
		fileWriter.close();}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

In the following example, the DummyReceivedMessageProcessor does not perform any operations.

$r_title(DummyReceivedMessgeProcessor.java)
import com.nexacro.xpush.api.ReceivedMessageProcessor;
import com.nexacro.xpush.fw.service.provider.PushMessage;

public class DummyReceivedMessageProcessor implements ReceivedMessageProcessor {
	public void receive(PushMessage receivedMessage) {
	}
}

Installation

Place the class that implemented the ReceivedMessageProcessor and other required resources (other classes, property files) into the "$XPUSH_HOME/lib/classes" directory. Or, package them in a jar file, and place it into the "$XPUSH_HOME/lib" directory. When starting X-PUSH, all jar files located in "$XPUSH_HOME/lib" are included in the class path to be used.

Setup

Set the class defined under the ReceivedMessageProcessorClassName attribute in the SocketPushMessageProvider service in the "xpush_config.xml" configuration file. An X-PUSH Server dynamically loads classes.

<service name="SocketPushMessageProvider">
	...
	<attribute name="ReceivedMessageProcessorClassName">
		ReceivedMessageProcessorSample
	</attribute>
	...
</service>

Even with ReceivedMessageProcessor that does not do anything, the preparation is operated if it is set up. It should not be set and left blank when not in use.

Pushed Message Processor Development

The Pushed Message Processor can be used to save records of sent messages or process other follow-up operations. For example, messages that were transmitted normally or abnormally can be stored in a file or database so that the administrator can identify which message is not being transmitted to a client for follow-up activities.

An X-PUSH Server calls the PushedMessageProcessor's process() method immediately after transmitting a message to a client. At this time, a client's UserProfile, message type, message ID, and message contents are included a list (byte[] format) and transmitted. The transmitted UserProfile is an object returned by the authenticator's authenticate() method. The message type and ID are provided by the message provider, and the list contains the delivered bit stream in a byte[] format. The byte[] values contained in a list are values passed from the message provider API to the pushMessage.addData() from messages actually delivered to clients. If two addData() values were called, the size of the list is 2 (List.size()), and there will be two byte[] values. If a message was modified through the use of the Message Formatter, the modified value will be contained in the list.

PushedMessageProcessor interface

Define the class that implements the "com.nexacro.xpush.fw.service.publish.PushedMessageProcessor" interface.

PushedMessageProcessor.process

If a message was transmitted normally:

public void process(UserProfile userProfile, final String action, final String topicType, final String topicId, String messageID, List<byte[]> valueBytesList)

The UserProfile returned as a result of authentication as well as the message topicType, topicId, messageID, and contents are contained in a list in the byte[] format, and they are called as parameters by an X-PUSH Server after transmitting a message to a client.

Parameters

userProfile

Object returned via the authenticator's authenticate() method.

action

Message type (PUSH/RELI)

topicType

Message type

topicId

Message key

messageID

Message ID

valueBytesLists

List containing message values in byte[] formats.

If Action is PUSH, messageID is NULL.

If a message was not transmitted normally:

public void process(UserProfile userProfile, final String action, final String topicType, final String topicId, String messageID, List<byte[]> valueBytesList , Exception exception)

The UserProfile returned as a result of authentication as well as the message topicType, topicId, and contents are contained in a list in the byte[] format, and they are called as parameters by an X-PUSH Server when an error occurs at the time of transmitting a message to a client.

Parameters

userProfile

Object returned via the authenticator's authenticate() method.

topicType

Message type

topicId

Message key

messageID

Message ID

valueBytesLists

List containing message values in byte[] formats.

Exception

Exception for failed message transmission contents.

Example

In the following example, DemoPushedMessageProcessor.java stores the contents of successful and failed message transmissions in files.

$r_title(DemoPushedMessageProcessor.java)
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;

import com.nexacro.xpush.fw.service.auth.UserProfile;
import com.nexacro.xpush.fw.service.publish.PushedMessageProcessor;

public class TestPushedMessageProcessor implements PushedMessageProcessor{

public void process(UserProfile userProfile, String action, String topicType,String topicId,
		List<byte[]> valueBytesList) {

		try 
		{
			File file = new File("./SuccessMessageList.txt");
			FileWriter fileWriter = new FileWriter(file);

			StringBuffer sb = new StringBuffer().append("SUCCESS USER ID :")
				.append(userProfile.getId()).append("projectID:")
				.append(userProfile.getProjectID())
				.append("Topic Type :")
				.append(topicType)
				.append(" Action:").append(action)
				.append(" Topic ID:").append(topicId)
                .append(" Meesage ID:").append(messageID).append("/n");

			fileWriter.write(sb.toString());
			fileWriter.close();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}

	public void process(UserProfile userProfile, String action, String topicType, 
		String topicId, String messageID, List<byte[]> valueBytesList, Exception exception) {
		try 
		{
			File file = new File("./FailMessageList.txt");
			FileWriter fileWriter = new FileWriter(file);

			StringBuffer sb = new StringBuffer().append("FAIL USER ID :")
				.append(userProfile.getId()).append("projectID:")
				.append(userProfile.getProjectID())
				.append(" Topic Type :").append(topicType)
				.append(" Action:").append(action)
				.append(" Topic ID:").append(topicId).append("/n")
                .append(" Meesage ID:").append(messageID).append("/n")
				.append("Exception Message :")
				.append(exception.getMessage());
			fileWriter.write(sb.toString());

			fileWriter.close();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
	public void process(UserProfile userProfile, String topicType,String topicId,
		List<byte[]> valueBytesList) {

		try 
		{
			File file = new File("./SuccessMessageList.txt");
			FileWriter fileWriter = new FileWriter(file);

			StringBuffer sb = new StringBuffer().append("SUCCESS USER ID :")
				.append(userProfile.getId()).append("Topic Type :")
				.append(topicType)
				.append(" Topic ID:").append(topicId);

			fileWriter.write(sb.toString());
			fileWriter.close();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}

	public void process(UserProfile userProfile, String topicType, 
		String topicId, List<byte[]> valueBytesList, Exception exception) {
		try 
		{
			File file = new File("./FailMessageList.txt");
			FileWriter fileWriter = new FileWriter(file);

			StringBuffer sb = new StringBuffer().append("FAIL USER ID :")
				.append(userProfile.getId()).append(" Topic Type :")
				.append(topicType)
				.append(" Topic ID:").append(topicId).append("/n")
				.append("Exception Message :")
				.append(exception.getMessage());
			fileWriter.write(sb.toString());

			fileWriter.close();
		}
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
}

In the following example, the DummyPushedMessageProcessor does not perform any operations.

$r_title(DummyPushedMessgeProcessor.java)
import java.util.List;

import com.nexacro.xpush.fw.service.auth.UserProfile;
import com.nexacro.xpush.fw.service.publish.PushedMessageProcessor;

public class DummyPushedMessageProcessor implements PushedMessageProcessor {

    
	public void process(UserProfile userProfile, final String action, final String topicType, final String topicId, String messageID, List<byte[]> valueBytesList)
    {
	}
	public void process(UserProfile userProfile, final String action, final String topicType, final String topicId, String messageID, List<byte[]> valueBytesList , Exception exception) 
    {
	}
	public void process(UserProfile userProfile, final String topicType, 
		final String topicId, List<byte[]> valueBytesList) {
	}
	public void process(UserProfile userProfile, final String topicType, 
		final String topicId, List<byte[]> valueBytesList , Exception exception) {
	}
}

Installation

Place the class that implemented the PushedMessageProcessor and other required resources (other classes, property files) into the "$XPUSH_HOME/lib/classes" directory. Or, package them in a jar file, and place it into the "$XPUSH_HOME/lib" directory. When starting X-PUSH, all jar files located in "$XPUSH_HOME/lib" are included in the class path to be used.

Setup

Set the class defined under the PushedMessageProcessorClassName attribute in the PublishContainerFactory service in the "xpush_config.xml" configuration file. An X-PUSH Server dynamically loads classes.

<service name="PublishContainerFactory">
	...
	<attribute name="PushedMessageProcessorClassName">
		MyPushedMessageProcessor
	</attribute>
	...
</service>

The PushedMessageProcessor is used whenever messages are sent to a client. Inadequate internal logic in the PushedMessageProcessor can have a direct impact on the performance of an X-PUSH Server.

If the PushedMessageProcessor is set up, preparatory work must be completed regardless of whether it's being used. If not being used, do not apply the settings, and leave the item blank.

Notification Formatter Development

The NotificationFormatter can set the contents of a notification that is transmitted when a mobile client is not connected to an X-PUSH Server. To include an additional message to a notification of a particular topic, for example, you can add user parameters using the NotificationFormatter.

An X-PUSH Server transmits a received message to the NotificationBuilderService, and if a mobile client is not connected to the X-PUSH Server, the NotificationBuilderService generates a notification to be transmitted. At this time, the NotificationBuilderService calls the format() method from the NotificationFormatter. In the case of reliable messages, the format() method with PushMessage as a parameter is called.

NotificationFormatter Abstract Class Implementation

Implement a class that inherits the "com.nexacro.xpush.service.notification.NotificationFormatter" abstract class.

You must add xpush-2.8.x.jar from the existing X-PUSH server.

NotificationFormatter.format()

public NotificationPayload format(PushMessage pushMessage, NotificationPayload payload)

The function is called if a message received from a provider needs to be transmitted to a client as a notification.

A push message that is transmitted when called contains the messageType, messageId, and message contents in a list in the byte[] format. This is the same format as a push message that is transmitted from a provider.

The contents of a notification to be delivered to a client for NotificationPayload.

Parameters

pushmessage

Push message received by X-PUSH from a provider.

payload

A notification to be transmitted to a mobile device by X-PUSH.

Example

In the following example, UserNotificationFormatter.java sets the properties of a received push message in NotificationPayload and adds user parameters.

$r_title(UserNotificationFormatter.java)
import com.nexacro.xpush.fw.service.provider.MobileNotification;
import com.nexacro.xpush.fw.service.provider.PushMessage;
import com.nexacro.xpush.service.notification.NotificationFormatter;
import com.nexacro.xpush.service.notification.type.NotificationPayload;

public class UserNotificationFormatter extends NotificationFormatter {

	public NotificationPayload format(PushMessage pushMessage, NotificationPayload payload) {
		String action = pushMessage.getActionType();
		payload.setCommand(action);
		
		String topicType = pushMessage.getTopicType();
		String topicId = pushMessage.getTopicId();
		
		payload.addParam(NotificationPayload.TOPIC_TYPE, topicType);
		payload.addParam(NotificationPayload.TOPIC_ID, topicId);

		String message_0 = null;
		String message_1 = null;
		try {
			message_0 = new String(pushMessage.getValueBytesList().get(0), "UTF-8");
			message_1 = new String(pushMessage.getValueBytesList().get(1), "UTF-8");
		} catch (UnsupportedEncodingException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		payload.addParam("NAME", message_0 );
		payload.addParam("PHONE", message_1 );
		
		payload.setMessage( message_0 +	"-custom-" + message_0 );

		if (payload.isTooBig()) {
			NotificationPayload summarized = summarize(payload);
			return summarized;
		}
		
		return payload;
	}

	
}

The following example shows the default implementation of NotificationFormatterSample.java setting the properties of a received push message in NotificationPayload.

$r_title(NotificationFormatterBasicImpl.java)
package com.nexacro.xpush.service.notification;

import com.nexacro.xpush.fw.service.provider.MobileNotification;
import com.nexacro.xpush.fw.service.provider.PushMessage;
import com.nexacro.xpush.service.notification.NotificationFormatter;
import com.nexacro.xpush.service.notification.type.NotificationPayload;

public class NotificationFormatterBasicImpl extends NotificationFormatter {

	public NotificationPayload format(PushMessage pushMessage, NotificationPayload payload) {
		String action = pushMessage.getActionType();
		payload.setCommand(action);

		String topicType = pushMessage.getTopicType();
		String topicId = pushMessage.getTopicId();

		payload.addParam(NotificationPayload.TOPIC_TYPE, topicType);
		payload.addParam(NotificationPayload.TOPIC_ID, topicId);

		if (payload.isTooBig()) {
			NotificationPayload summarized = summarize(payload);
			return summarized;
		}

		return payload;
	}
	public NotificationPayload format(MobileNotification arg0, NotificationPayload arg1) {
        return null;
    }
}
$r_title(NotificationFormatterCustom.java)
import com.nexacro.xpush.fw.service.provider.MobileNotification;
import com.nexacro.xpush.fw.service.provider.PushMessage;
import com.nexacro.xpush.service.notification.NotificationFormatter;
import com.nexacro.xpush.service.notification.type.NotificationPayload;

public class NotificationFormatterCustom extends NotificationFormatter{

	public NotificationPayload format(PushMessage pushMessage, NotificationPayload payload) {

		payload.setMessage(
					"data message\n:" 
					+ pushMessage.getDataArray()[0] + "\n:" 
					+ pushMessage.getDataArray()[1] + "\n:" 
					+ pushMessage.getDataArray()[2]	);

		if (payload.isTooBig()) {
			NotificationPayload summarized = summarize(payload);
			return summarized;
		}
		
		return payload;
	}
	
	public NotificationPayload format(MobileNotification arg0, NotificationPayload arg1) {
		return null;
	}

}

Installation

Place the class that implemented the NotificationFormatter and other required resources (other classes, property files) into the "$XPUSH_HOME/lib/classes" directory. Or, package them in a jar file, and place it into the "$XPUSH_HOME/lib" directory. When starting X-PUSH, all jar files located in "$XPUSH_HOME/lib" are included in the class path to be used.

Setup

Set the class defined under the NotificationFormatterName attribute in the NotificationBuilderService service in the "xpush_config.xml" configuration file. An X-PUSH Server dynamically loads classes.

<service name="NotificationBuilderService">
	...
	<attribute name="NotificationFormatterName">
		UserNotificationFormatter
	</attribute>
	...
</service>

Note that the "NotificationFormatterBasicImpl" under "NotificationFormatterName" must be configured even if you do not use the NotificationFormatter with specific settings.

The push message - transmitted as a parameter via the NotificationFormatter's format() method call - is a copy of the message that is used internally in X-PUSH. The modification of the push message has no effect on the contents of a message being delivered to a client. This is true for mobile notifications as well.

Detailed explanation of installation and settings


Description

1

Create a classes folder in the lib folder of $XPUSH-HOME

2

Compile and copy the MessageFormatter class implemented in the classes folder

(Copy the package if there is one)

3

Edit the package name and class name for the service corresponding to each Message Formatter.

Pushed Notification Processor Development

Pushed Notification Processor can log a record of delivered notifications or process other follow-up actions. For example, notifications that are delivered normally or abnormally are stored in DB or file so that the administrator can understand which messages are not delivered to which clients and proceed with the follow-up actions.

X-PUSH server calls the process() method of PushedNotificationProcessor set right after sending the notification to the client. At this time, the result, which is the result data of the notification sent to the client, Type and id of Topic, and Notification Payload data included in the notification are included.

PushedNotificationProcessor Interface

PushedNotificationProcessor interface exists as a different interface according to GCM and APNS.

In the case of GCM, the class that implemented com.nexacro.xpush.service.notification.PushedGcmNotificationProcessor must be defined, and in the case of APNS, the class that implemented com.nexacro.xpush.service.notification.PushedApnsNotificationProcessor must be defined.

PushedGcmNotificationProcessor

public void multiAppWithProjectIdProcess(GcmResult result, Topic topic, NotificationPayload payload, String projectId)

The follow-up action can be performed through GcmResult returned as the notification result, Topic information related to the notification content, and NotificationPayload containing the notification content.

Parameters

result

The object containing the notification result

1) errorCode: Notification result

2) message: Transmitted message

3) messageID: messageID of the reliable message

4) registrationID: Device token value

5) updateRegistrationID: When sent with a device token not registered in GCM

6) bundleID: Sender ID where the device token is registered

topic

The object containing the topic type and topic ID

payload

The object containing the payload of the notification

projectId

Project ID

The notification result can be checked through the errorCode of GcmResult. If the errorCode is Null, this is the result of the notification being successfully performed to the client.

The ERROR code can be one of the following values.

FCM Status Code

NoErrorsEncountered

No error occurred. Transmission successful

BadApiKey

Incorrect API Key

MissingRegistration

No Registration ID in the message sent to GCM

InvalidRegistration

Incorrect Registration ID format

MismatchSenderId

No Sender ID matching the corresponding Registration ID

NotRegistered

Registration ID not registered in GCM

MessageTooBig

Message size exceeding 4KB

InvalidDataKey

FCM reserved words used for message data key

InvalidTtl

TTL of the message exceeding the range of 0 ~ 22419200 (4 weeks)

Unavailable

GCM server unavailable

InternalServerError

GCM server internal error

InvalidPackageName

Registration ID indicating invalid package name

PushedApnsNotificationProcessor

public void  multiAppWithProjectIdProcess(ApnsResult result, Topic topic ,NotificationPayload payload, String projectId)

The follow-up action can be performed through ApnsResult returned as the notification result, Topic information related to the notification content, and NotificationPayload containing the notification content.

Parameters

result

The object containing the notification result

1) statusCode: Notification result

2) message: Transmitted message

3) messageID: messageID of the reliable message

4) device: Device token value

5) updateRegistrationID: Device token not registered in APNS or device token value received from the feedback service

6) bundleID: bundleID of the app where the device token is registered

topic

The object containing topic type and topic ID

payload

The object containing the payload of the notification

projectId

Project ID

The notification result can be checked through the statusCode of ApnsResult. If the statusCode is NoErrorEncountered, this is the result of the notification being successfully performed to the client.

The STATUS code can be one of the following values.

Apns Status Code

NoErrorsEncountered

No error occurred. Transmission successful

ProcessingError

APNS internal processing error

MissingDeviceToken

No Device Token in the message sent to APNS

MissingTopic

No Topic in the message sent to APNS

MissingPayload

No Payload in the message sent to APNS

InvalidTokenSize

Device Token size not being 32byte

InvalidTopicSize

Topic size error

InvalidPayloadSize

Invalid Device Token

Shutdown

APNS server Shutdown

Unknown

Unknown cause

Examples

The following PushedGcmNotificationProcessorImpl.java is an example of outputting the content of the delivered notification as a log.

$r_title(PushedGcmNotificationProcessorImpl.java)
import com.nexacro.xpush.fw.service.log.LogLevel;
import com.nexacro.xpush.message.Topic;
import com.nexacro.xpush.service.notification.type.GcmResult;
import com.nexacro.xpush.service.notification.type.NotificationPayload;

import jp.ossc.nimbus.core.ServiceManagerFactory;
import jp.ossc.nimbus.service.log.Logger;

public class PushedGcmNotificationProcessorImpl 
						implements PushedGcmNotificationProcessor {

	private Logger logger = ServiceManagerFactory.getLogger();
	
	@Override
	public void multiAppWithProjectIdProcess
				(GcmResult result, Topic topic, NotificationPayload payload, 
				String projectId) {
		logger.write(LogLevel.DEBUG
		,"Pushed Notification MultiApp GCM Process: "
		+ result.toString() + ", " 
		+ topic.toString()+ ", " 
		+ payload.toString());		
	}
}

The following PushedApnsNotificationProcessorImpl is an example of a notification sent to Apns.

$r_title(PushedApnsMessgeProcessor.java)

import com.nexacro.xpush.fw.service.log.LogLevel;
import com.nexacro.xpush.message.Topic;
import com.nexacro.xpush.service.notification.type.ApnsResult;
import com.nexacro.xpush.service.notification.type.NotificationPayload;

import jp.ossc.nimbus.core.ServiceManagerFactory;
import jp.ossc.nimbus.service.log.Logger;

public class PushedApnsNotificationProcessorImpl 
							implements PushedApnsNotificationProcessor {

	private Logger logger = ServiceManagerFactory.getLogger();

	@Override
	public void multiAppWithProjectIdProcess
				(ApnsResult result, Topic topic, NotificationPayload payload,
				 String projectId) {
		logger.write(LogLevel.DEBUG, 
		"Pushed Notification MultiApp Apns Process: "
		+ result.toString() + ", " 
		+ topic.toString() + ", " 
		+ payload.toString());
	}
}

Installation

Place the class implementing PushedNotificationProcessor and other necessary resources (other class, property file) in $XPUSH_HOME/lib/classes or package it as a jar file and place it in $XPUSH_HOME/lib. When X-PUSH is run, all jar files in $XPUSH_HOME/lib are included in the class path and used.

Settings

Set the class defined in attributes in GcmNotifierService and ApnsNotifierService in the setting file xpush_config.xml. X-PUSH server dynamically loads and uses the corresponding class.

<service name=""GcmNotifierService">
	...
	<attribute name="PushedGcmNotificationProcessorName">
		MyPushedGcmProcessor
	</attribute>
	...
</service>
 .......
 <service name=""ApnsNotifierService">
	...
	<attribute name="PushedApnsNotificationProcessorName">
		MyPushedApnsProcessor
	</attribute>
	...
</service>

PushedNotificationProcessor is used whenever a notification is sent to the client. Improper logic inside the PushedNotificationProcessor can directly affect the performance of the X-PUSH server.

Even with PushedNotificationProcessor that does not do anything, the preparation is operated if it is set up. It should not be set and left blank when not in use.

Crontab Scheduler Development

Task Interface

The class implementing the interface it.sauronsoftware.cron4j.Task is defined.

To further develop the scheduler, the $XPUSH_HOME/lib/cron4j-2.2.5.jar file must be added.

Examples

The following code is a sample code that deletes the normally transmitted message from the T_NOTIFICATION table when the corresponding scheduler is running.

package com.nexacro.xpush.service.schedule;

import java.sql.Connection;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import com.nexacro.xpush.fw.service.log.LogLevel;
import com.nexacro.xpush.service.dbConPool.DBCPService;

import it.sauronsoftware.cron4j.Task;
import it.sauronsoftware.cron4j.TaskExecutionContext;
import jp.ossc.nimbus.core.ServiceManagerFactory;
import jp.ossc.nimbus.service.log.Logger;


/*
 * Delete notifications past the expiration date every Sunday
 * 
 * 
 */


public class DeleteExpiredNotificationTask extends Task {

	private DBCPService dbcpService;
	private Logger logger = ServiceManagerFactory.getLogger();

	private int deleteCount = 0;

	public DBCPService getDbcpService() {
		return dbcpService;
	}

	public void setDbcpService(DBCPService dbcpService) {
		this.dbcpService = dbcpService;
	}

	@Override
	public void execute(TaskExecutionContext excute) throws RuntimeException {

		logger.write(LogLevel.INFO, "Start " + getClass().getName());

		try {

			Calendar cal = Calendar.getInstance();
			SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd000000");
			String today = format.format(cal.getTime());

			findExpiredNotification(today);

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void findExpiredNotification(String today) {

		String sql = " SELECT MESSAGE_ID FROM T_NOTIFICATION ";
		String sql2 = "SELECT EXPIRATION_DATE FROM T_MESSAGE WHERE MESSAGE_ID = ?  ";

		Connection conn = null;
		PreparedStatement selectMessageID = null;
		PreparedStatement selectExpireDate = null;

		ResultSet messageIdResult = null;
		ResultSet expireDateResult = null;
		try {
			conn = dbcpService.getConnection();

			selectMessageID = conn.prepareStatement(sql);
			messageIdResult = selectMessageID.executeQuery();

			while (messageIdResult.next()) {
				String message_ID = messageIdResult.getString(1);
				selectExpireDate = conn.prepareStatement(sql2);
				selectExpireDate.setString(1, message_ID);
				expireDateResult = selectExpireDate.executeQuery();
				
				// When there is data in the T_MESSAGE table
				if (expireDateResult.next()) {
					String expireDate = expireDateResult.getString(1);
					if (expireDate != null) {
						if (Long.parseLong(expireDate) <= (Long.parseLong(today))) {
							close(selectExpireDate, expireDateResult);
							deleteNotification(conn, message_ID);
						}
					}
				// When there is no data in the T_MESSAGE table (When the corresponding message ID cannot be found in the T_Message table)	
				}else {
					close(selectExpireDate, expireDateResult);
					deleteNotification(conn, message_ID);
				}
			}

		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {

			logger.write(LogLevel.INFO, "Delete Expire Notification Count = " + deleteCount);
			deleteCount = 0;

			close(conn, selectMessageID, messageIdResult);
		}
	}

	public void deleteNotification(Connection conn, String messageID) {
		String sql = " DELETE FROM T_NOTIFICATION WHERE MESSAGE_ID = ? ";
		PreparedStatement deletetatement = null;

		try {
			deletetatement = conn.prepareStatement(sql);
			deletetatement.setString(1, messageID);

			int count = deletetatement.executeUpdate();

			if (count > 0) {
				deleteCount += count;
			}

		} catch (SQLException e) {
			// TODO Auto-generated catch block
			logger.write(LogLevel.ERROR, "SQL = " + sql + ", Message: " + e.getMessage());
			try {
				conn.rollback();
			} catch (SQLException e1) {
				// TODO Auto-generated catch block
			}
		} finally {
			close(deletetatement, null);
		}

	}

	public void close(PreparedStatement psmt, ResultSet rs) {
		try {
			if (psmt != null) {
				psmt.close();
			}
			if (rs != null) {
				rs.close();
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void close(Connection conn, PreparedStatement psmt, ResultSet rs) {
		try {
			if (rs != null) {
				rs.close();
			}
			if (psmt != null) {
				psmt.close();
			}
			if (conn != null) {
				conn.close();
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

Installation

Place the class inheriting Task and other necessary resources (other class, property file) in $XPUSH_HOME/lib/classes or package it as a jar file and place it in $XPUSH_HOME/lib. When X-PUSH is run, all jar files in $XPUSH_HOME/lib are included in the class path and used.

Settings

A scheduler can be added by setting the package path in the object code in the CronTabScheduleService service tag of $XPUSH_HOME/conf/xpush_config.xml for the class that inherits Task. Also, the scheduler cycle can be set.

<service name="CronTabScheduleService"  
	code="com.nexacro.xpush.service.schedule.CronTabScheduleService">
....
	<invoke name="add">
		<argument type="java.lang.String">0 0 1 * 0</argument> 
		 <argument type="it.sauronsoftware.cron4j.Task">
 			<object code="com.nexacro.xpush.service.schedule.DeleteNotificationMessageTask">
					<attribute name="dbcpService">
                        <service-ref>#DbcpService</service-ref>
                   	 </attribute>
        	 </object>             	 			
		</argument>
	</invoke>

....
</service>