7.사용자 정의 인터페이스 개발

Edit

사용자 정의 인터페이스를 개발하기 위해서는 $XPUSH_HOME/lib/xpush-2.8.x.jar 와 $XPUSH_HOME/lib/nimbus-1.1.9.jar의 내부 클래스 파일을 참조해야 합니다.

7.1인증자(Authenticator) 개발

X-PUSH가 인증을 필요로 하는 곳은 외부에서 X-PUSH에 접속하는 3가지 서비스입니다.

외부에서의 접속요청을 담당하는 각 서비스는 실제 인증 처리를 X-PUSH 외부에 있는 인증자에게 요청합니다. 각 시스템별로 인증하는 방법과 로직이 달라서 이러한 클래스를 개발자가 직접 개발하여야 합니다.

인증을 담당하는 클래스는 com.nexacro.xpush.fw.service.auth.Authenticator 인터페이스를 구현해야 하고, 설정파일에 클래스를 명시해야 합니다. 인증이 필요할 경우 해당 클래스를 로드하여 인증을 요청합니다.

설정한 클래스는 jar파일로 만들어 $XPUSH_HOME/lib에 놓습니다. 설치하는 jar에는 구현한 인증자가 사용하는 모든 리소스가 담겨 있어야 합니다.

배포본에는 모든 Authenticator가 com.nexacro.xpush.fw.service.auth.DummyAuthenticator로 기본 설정되어 있습니다. 기타 사용할 수 있는 인증자 클래스는 아래와 같습니다.

클래스명

설명

DummyAuthenticator

모든 사용자의 접속 허용

UserPropertiesAuthenticator

$XPUSH_HOME/conf/user.properties 파일에 등록된 사용자 허용

UserPropertiesEncryptAuthenticator

$XPUSH_HOME/conf/user.properties 파일에 등록된 암호화된 값 갖는 사용자 허용

UserPropertiesAuthenticator는 /conf/user.properties 파일을 이용하여 인증합니다. user.properties에 등록되지 않은 사용자는 AuthenticateException을 발생시킨 후 로그인이 실패합니다.

이용자 설정 방법은 "이용자ID"="이용자PASSWORD" 입니다.

UserPropertiesEncryptAuthenticator 클래스는 $XPUSH_HOME/conf/user.properties에 암호화된 password를 입력하여 사용자 인증을 수행할 수 있습니다.

password 암호화에 대한 자세한 내용은 user.properties password 암호화 항목을 참조해주세요.

7.1.1Authenticator 인터페이스

X-PUSH가 제공하는 com.nexacro.xpush.fw.service.auth.Authenticator 인터페이스에는 인증요청을 위한 authenticate() 메소드와 사용자가 로그아웃할 경우 이를 기존 시스템에 알리기 위한 logout() 메소드 두 개가 선언되어 있습니다.

Authenticator.authenticate()

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

전달된 3개의 String을 가지고 인증을 처리합니다. 인증 자체가 실패하였을 경우 AuthenticateException을 던지고, 데이터베이스 연결이 실패하는 등의 시스템 오류일 경우 AuthenticateSystemException을 던집니다. 인증이 성공하였을 경우 com.nexacro.xpush.fw.service.auth.UserProfile을 상속받은 클래스를 반환합니다. X-PUSH 서버는 반환된 UserProfile을 변경하지 않고, Message Formatter와 인증자의 logout을 호출할 경우에 전달합니다. Message Formatter나, logout을 처리하기 위한 부가적인 정보가 필요할 경우 UserProfile을 상속받은 클래스를 정의하여 반환합니다.

Parameters

projectId

사용자 projectId

userId

사용자 id

password

사용자 password

Return value

UserProfile

UserProfile 클래스를 상속한 클래스.

Exception

AuthenticateException

인증에 실패한 경우에 발생합니다.

AuthenticateSystemException

인증 시스템에 오류 또는 장애가 있는 경우에 발생합니다.

Authenticator.logout()

public void logout (UserProfile userProfile) throws AuthenticateSystemException

X-PUSH 서버는 클라이언트가 접속을 끊을 경우 UerProfile을 파라미터로 설정해 logout 메소드를 호출합니다. X-PUSH 서버는 외부의 시스템에 클라이언트의 접속 종료를 통지하기 위하여 logout()을 호출할 뿐입니다. 따라서 logout()의 실행결과가 X-PUSH 서버의 동작에 영향을 끼치지 않습니다.

Parameters

UserProfile

사용자 정보를 담고 있는 클래스

Exception

AuthenticateSystemException

인증 시스템에 오류 또는 장애가 있는 경우에 발생합니다.

7.1.2예제

DummyAuthenticator는 단지 Authenticator 인터페이스만을 구현한 클래스입니다. Authenticate(), logout() 메소드는 전달된 파라미터에 대하여 아무런 로직처리를 하지 않고, 결과적으로 모든 입력에 대하여 인증된 것으로 처리합니다.

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);
        
        return userProfile; 
    }


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

}

7.1.3인증자 설치

Authenticator 인터페이스를 구현한 인증자 클래스와 기타 인증자가 필요한 자원(기타 클래스, property 파일) 등을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar 파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 있는 모든 jar 파일을 클래스패스에 포함해 사용합니다.

7.1.4인증자 설정

X-PUSH의 MiPlatformReliabilityNettyWithProjectIDProtocol, SocketProviderProtocol, MonitorProtocol 서비스는 인증에 필요할 경우 설정된 클래스를 로드하여 인증을 요청합니다. 이를 위하여 개발된 인증 클래스를 X-PUSH 설정파일 $XPUSH_HOME/conf/xpush_config.xml에 설정합니다.

개발한 인증자 클래스 설정은 다음과 같습니다.

MiPlatformProtocolReliabilityAuthenticator 서비스 설정

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

SocketProviderProtocolAuthenticator 서비스 설정

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

MonitorProtocol 서비스 설정

<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>

UserPropertiesEncryptAuthenticator는 /conf/user.properties를 이용하여 인증합니다. 패스워드는 암호화 되어 있으며, user.properties에 등록되지 않은 사용자는 AuthenticateException을 발생 시킨 후 login이 실패합니다.

설정한 클래스는 jar파일로 만들어 $XPUSH_HOME/lib에 놓습니다. 설치하는 jar에는 구현한 인증자가 사용하는 모든 리소스가 담겨 있어야 합니다.

배포본에는 모든 Authenticator가 com.nexacro.xpush.fw.service.auth.DummyAuthenticator로 기본 설정되어 있습니다. 기타 사용할 수 있는 인증자 클래스는 아래와 같습니다.

클래스명

설명

DummyAuthenticator

모든 사용자의 접속 허용

UserPropertiesRealiabilityAuthenticator

모든 사용자의 접속 허용

UserPropertiesAuthenticator

$XPUSH_HOME/conf/user.properties 파일에 등록된 사용자 허용

UserPropertiesEncryptAuthenticator

$XPUSH_HOME/conf/user.properties 파일에 등록된 암호화된 값 갖는 사용자 허용

UserPropertiesAuthenticator는 /conf/user.properties 파일을 이용하여 인증합니다. user.properties에 등록되지 않은 사용자는 AuthenticateException을 발생시킨 후 로그인이 실패합니다.

이용자 설정 방법은 "이용자ID"="이용자PASSWORD" 입니다.

UserPropertiesEncryptAuthenticator 클래스는 $XPUSH_HOME/conf/user.properties에 암호화된 password를 입력하여 사용자 인증을 수행할 수 있습니다.

password 암호화에 대한 자세한 내용은 user.properties password 암호화 항목을 참조해주세요.

7.2내장 메시지 공급자(Embedded Message Provider) 개발

Message Provider는 X-PUSH 서버와 별개로 동작하는 애플리케이션으로 개발하여야 합니다. 그러나 Embedded Message Provider 기능을 사용하면 X-PUSH 서버에 내장되어 메시지를 직접 공급할 수 있습니다. X-PUSH 서버가 Java로 구현되어 있으므로 embedded message provider방식은 Java로 개발해야 합니다.

Embedded Message Provider 기능을 사용하기 위해서는 실제 메시지를 공급하는 Actor 클래스를 개발하고, 이를 xpush_config.xml에 설정해야 합니다.

X-PUSH 서버는 구동된 후 설정된 Actor 클래스를 로드하여 start() 메소드를 호출합니다. 또한, shutdown 시에 stop() 메소드를 호출합니다.

7.2.1Actor 클래스

추상클래스 com.nexacro.xpush.api.EmbeddedMessageProviderActor를 상속받은 클래스를 구현합니다.

상속받은 클래스가 구현할 메소드는 start()와 stop()입니다.

void start()

X-PUSH 서버가 구동 시에 한 번만 호출합니다. start() 메소드는 메시지 공급을 위한 처리를 하고 곧바로 반환하여야 하며, 그렇지 않을 경우 X-PUSH 서버는 이후 처리를 하지 못하게 됩니다. 따라서 메시지 공급을 하기 위한 스레드를 start() 메소드 안에서 생성하고 구동한 후에 반환하여야 합니다.

start() 메소드가 blocking 될 경우 X-PUSH 서버가 동작하지 않습니다.

void stop()

X-PUSH 서버가 shutdown 할 경우 한번 호출합니다.

7.2.2예제

다음 코드는 1초에 한 번씩 현재 시간을 메시지로 push하는 샘플입니다. start() 메소드에서 스레드를 생성하여 구동하는 것과 providerMessage(PushMessage) 메소드를 사용하여 메시지를 공급하는 것을 볼 수 있습니다.

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 사용

경우에 따라서는 log를 직접 받아서 처리하여야 할 때가 있습니다. LogDispatcherService는 이처럼 로그를 직접 전달하기 위한 서비스입니다.

서비스를 처리하는 순서는 아래와 같습니다.

1. 인터페이스 LogProcessor를 구현한 클래스를 선언합니다.

public class LogProcessorSample implements LogProcessor {

2. 생성자 혹은 적당한 위치에서 자신을 LogDispatcherService에 등록합니다.

LogDispatcherService.register(this);

3. 인터페이스 LogProcessor에 정의된 processLog() 메소드를 구현합니다.

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

다음은 Embedded Message Provider에서 로그를 받아서 콘솔에 그대로 뿌리는 예입니다. 위에서 예로 든 UserMessageProviderActor를 수정하였고, LogProcessor를 위한 코드 부분만 발췌하였습니다.

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);
    }
}

7.2.3설치

EmbeddedMessageProviderActor를 상속한 클래스와 기타 필요한 자원(기타 클래스, property 파일)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar 파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 있는 모든 jar 파일을 클래스패스에 포함해 사용합니다.

7.2.4설정

EmbeddedMessageProviderActor를 상속한 클래스를 $XPUSH_HOME/conf/xpush_config.xml의 EmbeddedPushMessageProvider 서비스의 EmbeddedMessageProviderActorClassName 어트리뷰트에 설정합니다.

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

아무것도 하지 않는 EmbeddedMessageProvider라도 설정이 되어 있으면 이를 위한 준비작업을 하게 됩니다. 사용하지 않을 경우에는 설정하지 말고 비워두어야 합니다.

7.3메시지 변환(Message Formatter) 개발

Message Formatter는 클라이언트마다 다른 메시지를 보내거나, 클라이언트 별로 메시지를 필터링할 때 사용할 수 있습니다. 예를 들어 "XXX님 ..."과 같이 메시지에 클라이언트 사용자의 이름을 포함해야 할 경우에 각 클라이언트 사용자의 이름을 포함한 복수의 메시지를 메시지 공급자가 공급하지 않고, 한 개의 메시지에 대하여 각각의 클라이언트에 전송하기 전에 이름 부분 만을 해당 클라이언트의 사용자 이름으로 치환하여 전송할 수 있습니다.

X-PUSH 서버는 메시지를 클라이언트에게 전송하기 바로 직전에 설정된 MessageFormatter의 format() 메소드를 호출합니다. 이때 클라이언트의 UserProfile과 메시지의 type, id와 메시지 내용을 byte[] 형태로 List에 담아 전달합니다. 전달된 UserProfile은 인증자의 authenticate() 메소드가 반환한 객체이며 message type과 message id는 메시지 공급자가 공급한 메시지의 type과 id이며 List는 전달한 byte[]형태의 bit 스트림을 가지고 있습니다. List에 담긴 byte[]는 메시지 공급자 API에서 pushMessage.addData()로 넘겨준 값들로서, 두 번의 addData()가 호출되었다면 List의 크기(List.size())는 2이며, 두 개의 byte[]가 있습니다.

만약 MessageFormatter의 format() 메소드의 반환값이 null인 경우 X-PUSH 서버는 메시지를 클라이언트에게 전송하지 않습니다. 이를 이용하면 UserProfile의 속성값을 가지고 메시지 전송여부를 결정하는 필터링 기능을 구현할 수 있습니다. 또한, 적절한 로직에 의하여 복수의 메시지를 모아 한 번에 발송하는 그룹전달 등의 기능을 구현할 수 있습니다.

7.3.1MessageFormatter 인터페이스

인터페이스 com.nexacro.xpush.fw.service.publish.MessageFormatter를 구현한 클래스를 정의합니다.

X-PUSH 서버는 각 클라이언트에게 메시지를 전송하기 바로 직전에 설정된 MessageFormatter의 format() 메소드를 호출합니다. MessageFormatter는 파라매터로 전달된 UserProfile을 사용하여 메시지의 내용을 변환할 수 있고, 혹은 UserProfile에 따라서 메시지를 필터링할 수도 있습니다.

MessageFormatter.format()

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

인증결과로 반환한 UserProfile과 메시지 내용에 관련된 topicType, topicId와 메시지 내용을 byte[]형태로 담고 있는 List를 파라미터로, X-PUSH 서버가 메시지를 클라이언트에 전송하기 직전에 호출합니다. 반환된 List에 담긴 byte[]가 실제로 클라이언트에 전달됩니다. 만약 반환값이 null일 경우 클라이언트에게 전송하지 않습니다.

Parameters

userProfile

사용자 프로파일, 인증자가 authenticate()메소드로 반환한 객체

topicType

메시지 타입

topicId

메시지 키

valueBytesLists

메시지의 값들을 byte[] 형태로 가지고 있는 List

Return value

List<byte[]>

포멧된 값들을 byte[]형태로 가지고 있는 List

7.3.2예제

다음 NameMessagFormatter.java는 전달된 valueBytesList에 포함된 byte[]를 "utf-8"로 디코딩한 후, "userName"을 userProfile.getUserName()의 값으로 치환하는 예입니다. 치환된 문자열은 다시 "utf-8"로 인코딩되고 있습니다.

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;
    }
}

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;
    }
}

다음 DummyMessagFormatter는 아무런 변환을 하지 않는 예 입니다. 배포 시에 기본값으로 설정되어 있습니다.

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;
    }
}

7.3.3설치

MessageFormatter를 구현한 클래스와 기타 필요한 자원(기타 클래스, property 파일)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 잇는 모든 jar파일을 클래스패스에 포함해 사용합니다.

7.3.4설정

설정파일 xpush_config.xml의 MessageFormatterFactory 서비스의 MessageFormatterClassName 어트리뷰트에 정의한 클래스를 설정합니다. X-PUSH 서버는 해당 클래스를 동적으로 로딩하여 사용합니다.

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

MessageFormatter는 클라이언트로의 메시지 전송 때마다 사용됩니다. MessageFormatter 내부의 적절하지 않은 로직은 X-PUSH 서버의 성능에 직접 영향을 끼칠 수 있습니다.

아무것도 하지 않는 MessageFormatter라도 설정이 되어 있으면 이를 위한 준비작업을 하게 됩니다. 사용하지 않을 경우에는 설정하지 말고 비워두어야 합니다.

7.4공급 받은 메시지 후속 처리(Received Message Processor) 개발

7.4.1ReceivedMessageProcessor 인터페이스

인터페이스 com.nexacro.xpush.api.ReceivedMessageProcessor를 구현한 클래스를 정의합니다.

X-PUSH 서버는 각 프로바이더로부터 메시지를 전송받은 후에 ReceivedMessageProcessor의 receive() 메소드를 호출합니다. ReceivedMessageProcessor를 사용하여 전송된 메시지에 대한 기록을 남기거나 기타 후속 작업을 처리할 수 있습니다.

ReceivedMessageProcessor.receive()

public void receive(PushMessage pushmessage)

프로바이더로부터 전달받은 메시지를 메인 큐에 삽입하기 직전 호출됩니다.

호출 시 전달되는 PushMessage는 메시지 내용에 관련된 topicType, topicId와 메시지 내용을 byte[]형태로 담고 있는 List를 갖고 있습니다. 프로바이더에서 전달하는 PushMessage와 같은 형식입니다.

Parameters

pushmessage

X-PUSH가 프로바이더로부터 전달받은 푸시메시지

7.4.2예제

다음 ReceivedMessageProcessorSampleClass.java는 전달받은 PushMessage의 내용을 화면에 출력하는 예제 입니다.

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();
        }
    }
}

다음 DummyReceivedMessageProcessor 는 아무런 작업도 하지 않는 예 입니다.

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) {
    }
}

7.4.3설치

ReceivedMessageProcessor를 구현한 클래스와 기타 필요한 자원(기타 클래스, property 파일)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 잇는 모든 jar파일을 클래스패스에 포함해 사용합니다.

7.4.4설정

xpush_config.xml의 SocketPushMessageProvider 서비스의 ReceivedMessageProcessorClassName 어트리뷰트에 정의한 클래스를 설정합니다. X-PUSH 서버는 해당 클래스를 동적으로 로딩하여 사용합니다.

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

아무것도 하지 않는 ReceivedMessageProcessor라도 설정이 되어 있으면 이를 위한 준비작업을 하게 됩니다. 사용하지 않을 경우에는 설정하지 말고 비워두어야 합니다.

7.5전달된 메시지 후속 처리(Pushed Message Processor) 개발

Pushed Message Processor는 전달되는 메시지에 대한 기록을 남기거나 기타 후속 작업을 처리할 수 있습니다. 예를 들어 정상적으로 혹은 비정상적으로 전달된 메시지를 DB나 파일에 저장하여 관리자가 어떤 메시지가 어느 클라이언트에게 전달되지 않는지 파악해 후속 작업을 진행할 수 있도록 지원합니다.

X-PUSH 서버는 메시지를 클라이언트에게 전송 직후 설정된 PushedMessageProcessor의 process() 메소드를 호출합니다. 이때 클라이언트의 UserProfile과 메시지의 type, id와 메시지 내용을 byte[] 형태로 List에 담아 전달합니다. 전달된 UserProfile은 인증자의 authenticate() 메소드가 반환한 객체이며 message type과 message id는 메시지 공급자가 공급한 메시지의 type과 id이며 List는 전달한 byte[]형태의 bit 스트림을 가지고 있습니다. List에 담긴 byte[]는 실제로 클라이언트에 전달한 메시지로서 메시지 공급자 API에서 pushMessage.addData()로 넘겨준 값들로서, 두 번의 addData()가 호출되었다면 List의 크기(List.size())는 2이며, 두 개의 byte[]가 있습니다. 만약 MessageFormatter가 사용되어 메시지를 변경했다면, 변경된 값이 List에 담겨 있습니다.

7.5.1PushedMessageProcessor 인터페이스

인터페이스 com.nexacro.xpush.fw.service.publish.PushedMessageProcessor를 구현한 클래스를 정의합니다.

PushedMessageProcessor.process

정상적으로 메시지를 전달한 경우

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

인증결과로 반환한 UserProfile과 메시지 내용에 관련된 topicType, topicId, messageID와 메시지 내용을 byte[]형태로 담고 있는 List를 파라미터로, X-PUSH 서버가 메시지를 클라이언트에 전송 후에 호출합니다.

Parameters

userProfile

사용자 프로파일, 인증자가 authenticate()메소드로 반환한 객체

action

메시지의 타입(PUSH/RELI)

topicType

메시지 타입

topicId

메시지 키

messageID

메시지 아이디

valueBytesLists

메시지의 값들을 byte[] 형태로 가지고 있는 List

Action이 PUSH 이면, messageID는 NULL 입니다.

정상적으로 메시지를 전달하지 못한 경우

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

인증결과로 반환한 UserProfile과 메시지 내용에 관련된 topicType, TopicId와 메시지 내용을 byte[]형태로 담고 있는 List를 파라미터로, X-PUSH 서버가 메시지를 클라이언트에 전송 시에 오류가 발생할 경우 호출됩니다.

Parameters

userProfile

사용자 프로파일, 인증자가 authenticate()메소드로 반환한 객체

topicType

메시지 타입

topicId

메시지 키

messageID

메시지 아이디

valueBytesLists

메시지의 값들을 byte[] 형태로 가지고 있는 List

Exception

메시지 전송에 실패한 내용의 Exception.

7.5.2예제

다음 DemoPushedMessageProcessor.java는 전달된 Message의 내용을 파일에 성공된 메시지와 실패된 메시지를 각각의 파일에 남기는 예제 입니다.

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();
        }
    }
}

다음 DummyPushedMessageProcessor 는 아무런 작업도 하지 않는 예 입니다.

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) {
    }
}

7.5.3설치

PushedMessageProcessor를 구현한 클래스와 기타 필요한 자원(기타 클래스, property 파일)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 잇는 모든 jar파일을 클래스패스에 포함해 사용합니다.

7.5.4설정

설정파일 xpush_config.xml의 PublishContainerFactory 서비스의 PushedMessageProcessorClassName 어트리뷰트에 정의한 클래스를 설정합니다. X-PUSH 서버는 해당 클래스를 동적으로 로딩하여 사용합니다.

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

PushedMessageProcessor는 클라이언트로의 메시지 전송 때마다 사용됩니다. PushedMessageProcessor 내부의 적절하지 않은 로직은 X-PUSH 서버의 성능에 직접 영향을 끼칠 수 있습니다.

아무것도 하지 않는 PushedMessageProcessor라도 설정이 되어 있으면 이를 위한 준비작업을 하게 됩니다. 사용하지 않을 경우에는 설정하지 말고 비워두십시오.

7.6모바일 알림 메시지 변환(Notification Formatter) 개발

NotificationFormatter는 모바일 클라이언트가 X-PUSH 서버에 미접속 상태일 때 전달하는 Notification의 내용을 설정할 수 있습니다. 예를 들어 특정 Topic에 관한 Notification에 추가적인 메시지를 포함하고 싶을 경우 NotificationFormatter에서 사용자 파라미터를 추가할 수 있습니다.

X-PUSH 서버는 수신받은 메시지를 NotificationBuilderService 서비스에 전달하며, 모바일 클라이언트가 X-PUSH 서버에 미접속한 상태일 경우, NotificationBuilderService에서는 Notification을 생성하여 전달하게 됩니다. 이때 NotificationBuilderService에서 NotificationFormatter의 format() 메소드를 호출하는데 신뢰성 메시지일 경우에는 PushMessage를 파라미터로 하는 format() 메소드를 호출합니다.

7.6.1NotificationFormatter 추상 클래스 구현

추상클래스 com.nexacro.xpush.service.notification.NotificationFormatter를 상속받은 클래스를 구현합니다.

NotificationFormatter.format()

public NotificationPayload format(PushMessage pushMessage, NotificationPayload payload)

프로바이더로부터 전달받은 메시지를 클라이언트에 노티피케이션으로 전달해야할 경우 호출됩니다.

호출 시 전달되는 PushMessage는 메시지 내용에 관련된 messageType, messageId와 메시지 내용을 byte[]형태로 담고 있는 List를 갖고 있습니다. 프로바이더에서 전달하는 PushMessage와 같은 형식입니다.

NotificationPayload의 경우 클라이언트로 전달될 노티피케이션의 내용입니다.

Parameters

pushmessage

X-PUSH가 프로바이더로부터 전달받은 푸시메시지

payload

X-PUSH가 모바일 디바이스로 전송할 노티피케이션

7.6.2예제

다음 UserNotificationFormatter.java는 전달받은 PushMessage의 속성들을 NotificationPayload에 설정하고 사용자 Parameter를 추가하는 예제입니다.

NotificationFormatterSample.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;
    }

    
}

다음 NotificationFormatterBasicImpl.java는 전달받은 PushMessage의 속성들을 NotificationPayload에 설정하는 기본 구현입니다.

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;
    }
}

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 메시지 입니다.\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;
    }

}

7.6.3설치

NotificationFormatter를 구현한 클래스와 기타 필요한 자원(기타 클래스, property 파일)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 잇는 모든 jar파일을 클래스패스에 포함해 사용합니다.

7.6.4설정

xpush_config.xml의 NotificationiBuilderService 서비스의 NotificationFormatterName 어트리뷰트에 정의한 클래스를 설정합니다. X-PUSH 서버는 해당 클래스를 동적으로 로딩하여 사용합니다.

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

NotificationFormatter를 별도로 설정하여 사용하지 않을 경우에도 NotificationFormatterName 항목에 NotificationiFormatterBasicImpl를 설정하여 두셔야 합니다.

NotificationFormatter의 format()메소드 호출에서 parameter로 전달되는 PushMessage는 X-PUSH 내부에서 사용되는 메시지의 사본입니다. 해당 PushMessage의 수정은 X-PUSH 내부 동작에 영향을 끼치지 않습니다.

7.7모바일 알림 후속 처리(Pushed Notification Processor) 개발

Pushed Notification Processor는 전달되는 알림에 대한 기록을 남기거나 기타 후속 작업을 처리할 수 있습니다. 예를 들어 정상적으로 혹은 비정상적으로 전달된 알림을 DB나 파일에 저장하여 관리자가 어떤 메시지가 어느 클라이언트에게 전달되지 않는지 파악해 후속 작업을 진행할 수 있도록 지원합니다.

X-PUSH 서버는 알림을 클라이언트에게 전송 직후 설정된 PushedNotificationProcessor의 process() 메소드를 호출합니다. 이 때, 클라이언트에게 보낸 알림의 결과 데이터인 result와 Topic의 Type과 id 그리고 알림에 포함했던 Notification Payload 데이터가 담겨 있습니다.

7.7.1PushedNotificationProcessor 인터페이스

PushedNotificationProcessor 인터페이스는 GCM과 APNS에 따라 다른 인터페이스로 존재합니다.

GCM일 경우 PushedGcmNotificationProcessor를 구현한 클래스를 정의해야하며, APNS일 경우 PushedApnsNotificationProcessor를 구현한 클래스를 정의해야합니다.

PushedGcmNotificationProcessor

public void multiAppProcess(GcmResult result, Topic topic, NotificationPayload payload)

알림결과로 반환한 GcmResult와 알림 내용과 관련된 Topic 정보 그리고 알림 내용을 담고 있는 NotificationPayload를 통해 후속 작업을 수행할 수 있습니다.

Parameters

result

알림의 결과를 포함한 객체

1) errorCode : 알림의 수행 결과

2) message : 전송된 메세지

3) messageID: 신뢰성 메시지의 messageID

4) registrationID: 디바이스 토큰 값

5) updateRegistrationID : GCM에 등록되지 않은 디바이스 토큰으로 보낸 경우

6) bundleID: 디바이스 토큰이 등록된 발신자 ID

topic

토픽 타입과 토픽 아이디를 포함한 객체

payload

알림의 payload를 포함한 객체

알림에 대한 결과는 GcmResult의 errorCode를 통해 확인 할 수 있습니다. errorCode가 Null인 경우, 알림이 클라이언트에게 정상적으로 수행된 결과입니다.

ERROR 코드는 다음 값 중 하나입니다.

FCM Status Code

NoErrorsEncountered

발생한 에러 없음. 전송 성공

BadApiKey

잘못된 API Key

MissingRegistration

GCM으로 보낸 메시지에 Registraion ID가 없음

InvalidRegistration

Registraion ID 의 형식이 올바르지 않음

MismatchSenderId

해당 Registraion ID에 매칭되는 Sender ID가 없음

NotRegistered

GCM에 등록되지 않은 Registraion ID

MessageTooBig

메시지 크기가 4KB를 초과

InvalidDataKey

메시지의 데이터키에 FCM 예약어를 사용함

InvalidTtl

메시지의 TTL이 범위 0 ~ 22419200(4주)를 벗어남

Unavailable

GCM 서버 이용 불가

InternalServerError

GCM 서버 내부 오류

InvalidPackageName

Registration ID가 유효하지 않은 패키지명을 가리킴

PushedApnsNotificationProcessor

public void multiAppProcess(ApnsResult result, Topic topic, NotificationPayload payload)

알림결과로 반환한 ApnsResult와 알림 내용과 관련된 Topic 정보 그리고 알림 내용을 담고 있는 NotificationPayload를 통해 후속 작업을 수행할 수 있습니다.

Parameters

result

알림의 결과를 포함한 객체

1) statusCode : 알림의 수행 결과

2) message : 전송된 메세지

3) messageID: 신뢰성 메시지의 messageID

4) device: 디바이스 토큰 값

5) updateRegistrationID : APNS에 등록되지 않은 디바이스 토큰 혹은

feedback 서비스로 부터 받아 온 디바이스 토큰 값

6) bundleID: 디바이스 토큰이 등록된 앱의 bundleID

topic

토픽 타입과 토픽 아이디를 포함한 객체

payload

알림의 payload를 포함한 객체

알림에 대한 결과는 ApnsResult의 statusCode를 통해 확인 할 수 있습니다. statusCode가 NoErrorEncountered인 경우, 알림이 클라이언트에게 정상적으로 수행된 결과입니다.

STATUS 코드는 다음 값 중 하나입니다.

Apns Status Code

NoErrorsEncountered

발생한 에러 없음. 전송 성공

ProcessingError

APNS 내부 처리 에러

MissingDeviceToken

APNS로 보낸 메시지에 Device Token이 없음

MissingTopic

APNS로 보낸 메시지에 Topic이 없음

MissingPayload

APNS로 보낸 메시지에 Payload가 없음

InvalidTokenSize

Device Token의 크기가 32byte가 아님

InvalidTopicSize

Topic 크기 오류

InvalidPayloadSize

Device Token이 유효하지 않음

Shutdown

APNS 서버가 Shutdown 됨

Unknown

알수없는 원인

7.7.2예제

다음 PushedGcmNotificationProcessorImpl.java는 전달된 Notification의 내용을 로그로 출력한 예제입니다.

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 multiAppProcess
                (GcmResult result, Topic topic, NotificationPayload payload) 
    {
     logger.write(LogLevel.DEBUG,"Pushed Notification MultiApp GCM Process: "
    + result.toString() + ", " + topic.toString() + ", " + payload.toString());        
    }

다음 PushedApnsNotificationProcessorImpl은 Apns로 보낸 알림에 대한 예제입니다.

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 multiAppProcess
                (ApnsResult result, Topic topic, NotificationPayload payload) {
   logger.write(LogLevel.DEBUG,"Pushed Notification MultiApp Apns Process: "
 + result.toString() + ", " + topic.toString() + ", " + payload.toString());
    }
}

7.7.3설치

PushedNotificationProcessor를 구현한 클래스와 기타 필요한 자원(기타 클래스, property 파일)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 잇는 모든 jar파일을 클래스패스에 포함해 사용합니다.

7.7.4설정

설정파일 xpush_config.xml의 GcmNotifierService와 ApnsNotifierService에서 어트리뷰트에 정의한 클래스를 설정합니다. X-PUSH 서버는 해당 클래스를 동적으로 로딩하여 사용합니다.

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

PushedNotificationProcessor는 클라이언트에게 알림을 전송 때마다 사용됩니다. PushedNotificationProcessor 내부의 적절하지 않은 로직은 X-PUSH 서버의 성능에 직접 영향을 끼칠 수 있습니다.

아무것도 하지 않는 PushedNotificationProcessor라도 설정이 되어 있으면 이를 위한 준비작업을 하게 됩니다. 사용하지 않을 경우에는 설정하지 말고 비워두십시오.

7.8모바일 알림 메시지 실패 후속 처리(Etc Processor) 개발

EtcProcessor는 Notification 전송이 실패했을 경우, 인터페이스의 구현을 통해 SMS 혹은 기타 메신저 서비스를 적용할 수 있도록 지원합니다.

X-PUSH 서버는 클라이언트에게 전송 직후 실패한 알림에 대해 EtcProcessor의 process() 메소드를 호출합니다. 이 때, 클라이언트에게 보낸 알림의 메시지, 유저 아이디, 토픽 정보, 디바이스 정보를 확인 할 수 있으며 이에 따른 후속 조치를 수행할 수 있습니다.

7.8.1EtcProcessor 인터페이스

인터페이스 com.nexacro.xpush.service.EtcService.EtcProcessor를 구현한 클래스를 정의합니다.

요청한 알림이 실패한 경우

public void notiFailProcess(String messageID, List<String> messages, Topic topic, DeviceInfo deviceInfo) throws ProcessFailException

notiFailprocess 메소드의 failList type인 NotificationFailMessage 객체에는 다음과 같은 값을 확인 할 수 있습니다.

Parameters

MessageID

전송 실패한 notification의 messageID

(해당 메시지ID는 t_message에서 확인 할 수있습니다.)

messsages

Provider에서 전송한 message 리스트

Topic

provider에서 전송한 메시지의 토픽(topic_type, topic_id)

DeviceInfo

전송실패한 디바이스 정보


projectID : 프로젝트 ID

userID: 사용자 ID

token : 디바이스 토큰

bundleID : 프로젝트, 앱 식별자

osVersion : 모바일 OS Version

os : 1(iOS), 2(Android)

errorCode : 실패한 에러코드

ERROR 코드는 FCM, APNS 각각 다르며 다음 값 중 하나입니다.

FCM Status Code

NoErrorsEncountered

발생한 에러 없음. 전송 성공

BadApiKey

잘못된 API Key

MissingRegistration

GCM으로 보낸 메시지에 Registraion ID가 없음

InvalidRegistration

Registraion ID 의 형식이 올바르지 않음

MismatchSenderId

해당 Registraion ID에 매칭되는 Sender ID가 없음

NotRegistered

GCM에 등록되지 않은 Registraion ID

MessageTooBig

메시지 크기가 4KB를 초과

InvalidDataKey

메시지의 데이터키에 FCM 예약어를 사용함

InvalidTtl

메시지의 TTL이 범위 0 ~ 22419200(4주)를 벗어남

Unavailable

GCM 서버 이용 불가

InternalServerError

GCM 서버 내부 오류

InvalidPackageName

Registration ID가 유효하지 않은 패키지명을 가리킴

Apns Error Code

NoErrorsEncountered

발생한 에러 없음. 전송 성공

ProcessingError

APNS 내부 처리 에러

MissingDeviceToken

APNS로 보낸 메시지에 Device Token이 없음

MissingTopic

APNS로 보낸 메시지에 Topic이 없음

MissingPayload

APNS로 보낸 메시지에 Payload가 없음

InvalidTokenSize

Device Token의 크기가 32byte가 아님

InvalidTopicSize

Topic 크기 오류

InvalidPayloadSize

Device Token이 유효하지 않음

Shutdown

APNS 서버가 Shutdown 됨

Unknown

알수없는 원인

7.8.2예제

다음 EtcPocessorImpl 클래스는 실패된 Notification의 정보를 출력하는 예제입니다. 이 후, 실패된 알림의 기타 메시저 서비스 및 SMS 등을 이용하여 후속처리를 수행할 수 있습니다.

EtcProcessorImpl.java

public class EtcProcessorImpl implements EtcProcessor {

    private final Logger logger = ServiceManagerFactory.getLogger();

    @Override
    public void notiFailProcess(String messageID, List<String> messages, Topic topic, DeviceInfo deviceInfo)  throws ProcessFailException {
        
                System.out.println(deviceInfo.getToken());
                System.out.println(deviceInfo.getUserID());
                System.out.println(deviceInfo.getOs());
                System.out.println(deviceInfo.getErrorCode());
                System.out.println(messageID);
                System.out.println(topic.getType());
                System.out.println(topic.getId());


                // UserID, ERROR_CODE, Device_Token 등을 통해 후속처리
                
        }  
    }

7.8.3설치

EtcProcessor를 구현한 클래스와 기타 필요한 자원(기타 클래스)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 잇는 모든 jar파일을 클래스패스에 포함해 사용합니다.

EtcProcessor에 대한 사용여부를 적용했다면 클라이언트에게 알림을 전송 때마다 사용됩니다. EtcProcessor 내부의 적절하지 않은 로직은 X-PUSH 서버의 성능에 직접 영향을 끼칠 수 있습니다.

아무것도 하지 않는 EtcProcessor라도 설정이 되어 있으면 이를 위한 준비작업을 하게 됩니다. 사용하지 않을 경우에는 설정하지 말고 비워두십시오.

7.8.4설정

설정파일 xpush_config.xml의 EtcService항목에서 어트리뷰트에 정의된 클래스를 설정합니다. X-PUSH 서버는 해당 클래스를 동적으로 로딩하여 사용합니다.

<service name="EtcService">
    <attribute name="EtcHandlerThreadPoolCount">1</attribute>
    <attribute name="EtcHandlerProcessingAtOnceCount">1</attribute>
    ....
    <attribute name="EtcProcessorName">
        com.nexacro.xpush.service.EtcService.EtcProcessorImpl
    </attribute>
</service>
<service name="GcmNotifierService">
   ...
    <attribute name="failOver">true</attribute>
   ...
</service>
<service name="ApnsNotifierService">
   ...
    <attribute name="failOver">true</attribute>
   ...
</service>

EtcProcessor 인터페이스를 사용하기 위해 APNs,FCM의 FailOver 어트리뷰트 값이 반드시 true로 설정되어 있어야 합니다.

아무것도 하지 않는 PushedNotificationProcessor라도 설정이 되어 있으면 이를 위한 준비작업을 하게 됩니다. 사용하지 않을 경우에는 설정하지 말고 비워두십시오.

7.9스케줄러(Crontab Scheduler) 개발

7.9.1Task 인터페이스

인터페이스 it.sauronsoftware.cron4j.Task를 구현한 클래스를 정의합니다.

추가로 스케줄러를 개발하기 위해, $XPUSH_HOME/lib/cron4j-2.2.5.jar 파일을 추가해야합니다.

7.9.2예제

다음 코드는 해당 스케쥴러가 작동하는 시점에, T_NOTIFICATION 테이블에서 정상적으로 전송한 메시지를 삭제하는 샘플 코드입니다.

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;


/*
 * 매 주 일요일에 만료된 날짜가 지난 알림에 대해 삭제
 * 
 * 
 */


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();
                
                // T_MESSAGE 테이블에 데이터가 있는 경우
                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);
                        }
                    }
                // T_MESSAGE 테이블에 데이터가 없는 경우 (T_Message 테이블에서 해당 메시지아이디를 못찾는 경우)    
                }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();
        }
    }
}

7.9.3설치

Task를 상속한 클래스와 기타 필요한 자원(기타 클래스, property 파일)을 $XPUSH_HOME/lib/classes에 가져다 놓거나, jar 파일로 패키징하여 $XPUSH_HOME/lib에 놓습니다. X-PUSH는 구동 시에 $XPUSH_HOME/lib에 있는 모든 jar 파일을 클래스패스에 포함해 사용합니다.

7.9.4설정

Task를 상속한 클래스를 $XPUSH_HOME/conf/xpush_config.xml의 CronTabScheduleService 서비스 태그에서 object code에 패키지 경로를 설정하여 스케줄러를 추가할 수 있습니다. 또한, 스케줄러 주기를 설정 할 수 있습니다.

<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>