FileDocCategorySizeDatePackage
PPSMSAdapter.javaAPI DocphoneME MR2 API (J2ME)11771Wed May 02 18:00:44 BST 2007com.sun.j2me.payment

PPSMSAdapter.java

/*
 *   
 *
 * Copyright  1990-2007 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt).
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */


package com.sun.j2me.payment;

import com.sun.midp.security.*;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.wireless.messaging.BinaryMessage;
import javax.wireless.messaging.Message;
import javax.wireless.messaging.MessageConnection;
import javax.wireless.messaging.TextMessage;

/**
 *
 * @created    June 9, 2005
 * @version    1.4
 */

/**
 * This Premium Priced SMS (PPSMS) Adapter sends messages to a PPSMS number,
 * which defines a payment model. The value of a transaction is determined
 * by the premium priced number to which the SMS is sent and/or by the content
 * described in the body of that SMS.
 */
public class PPSMSAdapter extends PaymentAdapter {
    private static final int SMS_LENGTH = 140;
    
    /** 
     * This class has a different security domain than the MIDlet suite 
     */
    private static SecurityToken classSecurityToken;
    
    /**
     * Initializes the security token for this class, so it can
     * perform actions that a normal MIDlet Suite cannot.
     *
     * @param token security token for this class.
     */
    public static void initSecurityToken(SecurityToken token) {
        if (classSecurityToken != null) {
            return;
        }

        classSecurityToken = token;
    }
    
    /**
     * Separate thread conducting the transaction
     */
    private class PPSMSThread extends Thread {
        private Transaction transaction;
        
        /**
         * Creates a new instance of PPSMSThread
         */
        public PPSMSThread(Transaction transaction) {
            this.transaction = transaction;
        }
        
        /**
         * Sends Premium Priced SMS to a number from adapter configuration
         */
        public void run() {
            
            byte[] payload = transaction.getPayload();
            
            Object[] specInfo = parseAndValidateSpecInfo(
                    transaction.getSpecificPriceInfo());
            
            String msisdn = (String) specInfo[0];
            String prefix = (String) specInfo[1];
            int smsCount = 1;
            
            if (specInfo.length > 2) {
                smsCount = ((Integer) specInfo[2]).intValue();
            }
            
            try {
                Message msg = null;
                MessageConnection conn =
                        (MessageConnection) Connector.open("sms://" + msisdn);
                               
                byte[] prefixBytes = prefix.getBytes("UTF-8");
                int messageLength = prefixBytes.length + 
                        (payload == null ? 0 : payload.length);
                if (messageLength > SMS_LENGTH) {
                    // IMPL_NOTE correcly process exception
                    throw new Exception("Message to be sent is too long");
                }
                byte[] message = new byte[messageLength];
                
                System.arraycopy(prefixBytes, 0, message, 0,
                        prefixBytes.length);
                
                if (payload != null) {
                    System.arraycopy(payload, 0, message, prefixBytes.length,
                            payload.length);
                }
                
                msg = (BinaryMessage)
                conn.newMessage(MessageConnection.BINARY_MESSAGE);
                
                ((BinaryMessage) msg).setPayloadData(message);
                
                for (int i = 0; i < smsCount; i++) {
                    conn.send(msg);
                }
                
                conn.close();
                transaction.setState(Transaction.SUCCESSFUL);
            } catch (Exception e) {
                // IMPL_NOTE correcly process exception
                // ErrorForm errorForm = new ErrorForm(transaction);
                // preemptDisplay(classSecurityToken, errorForm);
                
                // the transaction failed
                transaction.setState(Transaction.FAILED);
            }
            
            transaction.setWaiting(false);
        }
    }
    
    /**
     * Displays error message in case that SMS cannot be sent
     */
    private class ErrorForm extends Form implements CommandListener {
        private Transaction transaction;
        
        private final Command okCommand = new Command("Ok", Command.OK, 1);
        
        /**
         * Creates a new instance of ErrorForm
         */
        public ErrorForm(Transaction transaction) {
            super("Communication Error!");
            append("The Premium Priced SMS cannot be sent");
            this.transaction = transaction;
            addCommand(okCommand);
            setCommandListener(this);
        }
        
        public void commandAction(Command c, Displayable d) {
            preemptDisplay(classSecurityToken, null);
            
            // the transaction failed
            transaction.setState(Transaction.FAILED);
            transaction.setNeedsUI(false);
        }
    }
    
    /**
     * Creates a new instance of PPSMSAdapter
     */
    private PPSMSAdapter() {
    }
    
    /**
     * Gets a display name of this adapter.
     *
     * @return the display name
     */
    public String getDisplayName() {
        return "Premium Priced SMS";
    }
    
    /**
     * Gets a new instance of this adapter.
     *
     * @param configuration configuration info of PPSMSAdapter
     * @return the new instance of PPSMSAdapter
     */
    public static PPSMSAdapter getInstance(String configuration) {
        return new PPSMSAdapter();
    }
    
    /**
     * Validates the price information which are specified in the application
     * manifest file for the provider handled by this adapter. It throws an
     * <code>PaymentException</code> if the parameters are incorrect.
     *
     * @param price the price to pay when using this provider
     * @param paySpecificPriceInfo the specific price information string from 
     *      the manifest
     * @throws PaymentException if the provided information is correct
     */
    public void validatePriceInfo(double price,
            String paySpecificPriceInfo) throws PaymentException {
        
        if ((paySpecificPriceInfo == null) ||
                (paySpecificPriceInfo.length() == 0) ||
                parseAndValidateSpecInfo(paySpecificPriceInfo) == null) {
            throw new PaymentException(
                    PaymentException.INVALID_PRICE_INFORMATION, 
                    paySpecificPriceInfo);
        }
              
    }
    
    /**
     * Parses and validates specific payment information from input parameter
     *
     * @param paySpecificInfo the string representing comma-separated
     * specific payment information
     * @return array of specific information if all information is valid,
     * otherwise <code>null</code>
     */
    private Object[] parseAndValidateSpecInfo(String paySpecificInfo) {
        Object[] info = null;
        int firstIndex = paySpecificInfo.indexOf(',');
        
        if (firstIndex == -1) {
            return null;
        }
        
        int lastIndex = paySpecificInfo.lastIndexOf(',');
        String msisdn = paySpecificInfo.substring(0, firstIndex).trim();
        
        // check if msisdn is in right format
        try {
            long i = msisdn.startsWith("+")
            ? Long.parseLong(msisdn.substring(1))
            : Long.parseLong(msisdn);
        } catch (NumberFormatException nfe) {
            return null;
        }
        
        String prefix = "";
        String smsCount = null;
        
        // no number of messages parameter
        if (firstIndex == lastIndex) {
            prefix = paySpecificInfo.substring(firstIndex + 1,
                    paySpecificInfo.length()).trim();
        } else {
            prefix = paySpecificInfo.substring(firstIndex + 1,
                    lastIndex).trim();
            
            smsCount = paySpecificInfo.substring(lastIndex + 1,
                    paySpecificInfo.length()).trim();
        }
        
        // check if prefix meets all conditions described in specification
        if (!prefix.equals("")) {
            int maxLength = 8;
            if (prefix.startsWith("0x") || prefix.startsWith("0X")) {
                if (prefix.length() % 2 != 0) {
                    // the heximal value must have an even length
                    return null;
                }
                try {
                    prefix = prefix.substring(2);
                    // if prefix == 0xFFFFFFFFFFFFFFFF (allowed value),
                    // Long.parseLong would throw an exception, thus the
                    // string prefix has to be divided up
                    if (prefix.length() >= maxLength) {
                       Long.parseLong(prefix.substring(0, 7), 16);
                       Long.parseLong(prefix.substring(8), 16);
                    } else {
                        Long.parseLong(prefix, 16);
                    }
                    maxLength = 16;
                } catch (NumberFormatException nfe) {
                    return null;
                }
            }
            
            if (prefix.length() > maxLength) {
                return null;
            }
        }
        
        if (smsCount == null) {
            info = new Object[2];
        } else {
            info = new Object[3];
            try {
                info[2] = Integer.valueOf(smsCount);
            } catch (NumberFormatException nfe) {
                return null;
            }
        }
        
        info[0] = msisdn;
        info[1] = prefix;
        
        return info;
    }
    
    /**
     * Processes the given transaction, updates its state and returns the same
     * transaction instance or a new one (an instance of 
     * a <code>Transaction</code> subclass), which is based on the old 
     * transaction, but adds more (adapter specific) information to it.
     *
     * @param transaction the transaction to be processed
     * @return the transaction after processing
     */
    public Transaction process(Transaction transaction) {
        
        switch (transaction.getState()) {
            case Transaction.ASSIGNED:
                Thread thread = new PPSMSThread(transaction);
                transaction.setWaiting(true);
                thread.start();
                break;
                
            default:
                return super.process(transaction);
        }
       
        return transaction;
    }
}