0001: /*
0002: *
0003: *
0004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: */
0026:
0027: package com.sun.j2me.payment;
0028:
0029: import javax.microedition.payment.TransactionListener;
0030: import javax.microedition.payment.TransactionRecord;
0031: import javax.microedition.payment.TransactionModuleException;
0032:
0033: import com.sun.midp.security.*;
0034:
0035: import com.sun.midp.util.DateParser;
0036: import com.sun.midp.io.Util;
0037:
0038: import java.io.*;
0039: import java.util.Hashtable;
0040: import java.util.Vector;
0041: import javax.microedition.lcdui.*;
0042: import javax.microedition.io.Connector;
0043: import javax.microedition.io.Connection;
0044: import javax.microedition.io.HttpConnection;
0045: import javax.microedition.io.ConnectionNotFoundException;
0046:
0047: /**
0048: * This class represents a payment module. An instance of this class accepts
0049: * payment requests from transaction modules. For each payment request it
0050: * creates an instance of the <code>Transaction</code> class, set itself as
0051: * an initial transaction processor of this transaction and adds it to the
0052: * internal queue.
0053: * <p>
0054: * The internal queue is processed by a background thread created by the
0055: * instance of the payment module. This thread iterates through the queued
0056: * transactions and calls their <code>process</code> methods. This changes the
0057: * internal states of the transactions until they are fully processed and are
0058: * removed from the queue.
0059: * <p>
0060: * The <code>process</code> method of a transaction delegates the call to the
0061: * associated transaction processor. This is initially the payment module. That
0062: * allows him to show a provider selection form. After the user selects the
0063: * provider for the transaction, the payment module changes the transaction
0064: * processor of the transaction to the provider's payment adapter. This adapter
0065: * gets the control over the transaction till it finishes the transaction and
0066: * returns the control over it back to the payment module. The payment module
0067: * finally removes the transaction from the transaction queue and notifies the
0068: * application about the result of the payment.
0069: * <p>
0070: * The notification is done by creating a new transaction record for the
0071: * finished transaction and adding it to the transaction notification
0072: * queue which is processed by an extra thread which handles the notifications.
0073: * Each transaction record is automatically added to the transaction store, so
0074: * it can be obtained after crashing of the emulator.
0075: *
0076: * @version
0077: */
0078: public abstract class PaymentModule implements TransactionProcessor {
0079: /**
0080: * Inner class to request security token from SecurityInitializer.
0081: * SecurityInitializer should be able to check this inner class name.
0082: */
0083: static private class SecurityTrusted implements
0084: ImplicitlyTrustedClass {
0085: };
0086:
0087: /** This class has a different security domain than the MIDlet suite. */
0088: private static SecurityToken classSecurityToken = SecurityInitializer
0089: .requestToken(new SecurityTrusted());
0090:
0091: /** Standard timeout for alerts. */
0092: private static final int ALERT_TIMEOUT = 1250;
0093:
0094: /** The property that contains payment module class name. */
0095: private static final String PAYMENT_MODULE = "microedition.payment.paymentmodule";
0096:
0097: /** Payment module singleton. */
0098: private static PaymentModule paymentModule;
0099:
0100: /** Transaction processing thread. */
0101: private TransactionProcessingThread processingThread;
0102:
0103: /** Transaction state notification thread. */
0104: private TransactionNotificationThread notificationThread;
0105:
0106: /**
0107: * Creates a new instance of PaymentModule.
0108: */
0109: protected PaymentModule() {
0110: }
0111:
0112: /**
0113: * Returns an instance of the <code>PaymentModule</code> class. It creates
0114: * only one instance and reuses it each time the method is called.
0115: *
0116: * @return the instance
0117: */
0118: public static PaymentModule getInstance() {
0119: if (paymentModule == null) {
0120: String className = System.getProperty(PAYMENT_MODULE);
0121: if (className != null) {
0122: try {
0123: paymentModule = (PaymentModule) Class.forName(
0124: className).newInstance();
0125: } catch (ClassNotFoundException cnfe) {
0126: // intentionally ignored
0127: } catch (InstantiationException ie) {
0128: // intentionally ignored
0129: } catch (IllegalAccessException iae) {
0130: // intentionally ignored
0131: }
0132: }
0133: // if (paymentModule == null) {
0134: // paymentModule = new PaymentModule();
0135: // }
0136: }
0137: return paymentModule;
0138: }
0139:
0140: /**
0141: * It's a factory method for <code>TransactionModuleImpl</code>.
0142: *
0143: * @param object the application MIDlet initiating a payment transaction
0144: * @return a new instance of a <code>TransactionModuleImpl</code> subclass.
0145: * @throws TransactionModuleException indicates a creation failure
0146: */
0147: public abstract TransactionModuleImpl createTransactionModule(
0148: Object object) throws TransactionModuleException;
0149:
0150: /**
0151: * Returns an instrance of the <code>TransactionStore</code> wich is used
0152: * for storing of all transaction records produced in the payment module.
0153: * There is only one such instance, which is returned each time the method
0154: * is called. This instance is used from both internal threads of the
0155: * payment module (<code>TransactionProcessingThread</code> and
0156: * <code>TransactionNotificationThread</code>) and it is left to an
0157: * implementation of the <code>TransactionStore</code> to be thread safe.
0158: *
0159: * @return the transaction store
0160: */
0161: protected abstract TransactionStore getTransactionStore();
0162:
0163: /**
0164: * Returns an instance of <code>Utils</code> subclass.
0165: *
0166: * @return the instance
0167: */
0168: protected abstract Utils getUtilities();
0169:
0170: /**
0171: * Returns a new application ID, which can be used to store transaction
0172: * records to the transaction store.
0173: *
0174: * @return the new application ID
0175: * @throws IOException if there is a I/O failure while working with the
0176: * store
0177: */
0178: public final int getNextApplicationID() throws IOException {
0179: return getTransactionStore().getNextApplicationID();
0180: }
0181:
0182: /**
0183: * Returns the associated payment information for the given transaction.
0184: *
0185: * @param transaction the transaction
0186: * @return the payment information
0187: */
0188: protected static PaymentInfo getPaymentInfo(Transaction transaction) {
0189: return transaction.getTransactionModule().getPaymentInfo();
0190: }
0191:
0192: /**
0193: * Saves the payment information for the given transaction into storage.
0194: *
0195: * @param transaction the transaction
0196: */
0197: protected static void savePaymentInfo(Transaction transaction) {
0198: try {
0199: transaction.getTransactionModule().savePaymentInfo();
0200: } catch (IOException e) {
0201: // ignore
0202: }
0203: }
0204:
0205: /**
0206: * This class represents a transaction processing thread. It iterates
0207: * through the queued transactions and executes the <code>process</code>
0208: * method on them.
0209: */
0210: private class TransactionProcessingThread extends Thread {
0211:
0212: /** List of transactions being processing. */
0213: private Transaction[] transactionQueue = new Transaction[16];
0214:
0215: /** Processing thread exit flag. */
0216: private boolean finished;
0217:
0218: /** Wait for next transaction flag. */
0219: private boolean wait = true;
0220:
0221: /**
0222: * This method is run when the thread starts. It implements the
0223: * selection and execution of the queued transactions.
0224: */
0225: public void run() {
0226: // transaction processing queue
0227: while (!finished) {
0228: boolean blockUI = false;
0229: for (int i = 0; i < transactionQueue.length; ++i) {
0230: Transaction transaction = transactionQueue[i];
0231: if (transaction == null) {
0232: continue;
0233: }
0234: if (blockUI && transaction.needsUI()) {
0235: continue;
0236: }
0237: if (transaction.needsUI()) {
0238: blockUI = true;
0239: }
0240: if (transaction.isWaiting()) {
0241: continue;
0242: }
0243:
0244: transaction = transaction.process();
0245: if ((transaction != null) && transaction.needsUI()) {
0246: blockUI = true;
0247: }
0248:
0249: transactionQueue[i] = transaction;
0250: wait = false;
0251: }
0252:
0253: synchronized (transactionQueue) {
0254: if (wait) {
0255: try {
0256: transactionQueue.wait();
0257: } catch (InterruptedException e) {
0258: }
0259: }
0260: wait = true;
0261: }
0262: }
0263: }
0264:
0265: /**
0266: * Adds a transaction to the transaction queue and makes the
0267: * transaction processing thread to continue its work.
0268: *
0269: * @param transaction the transaction
0270: * @throws TransactionModuleException if there is no more space for the
0271: * new transaction in the queue
0272: */
0273: public void addTransaction(Transaction transaction)
0274: throws TransactionModuleException {
0275: int i;
0276: for (i = 0; i < transactionQueue.length; ++i) {
0277: if (transactionQueue[i] == null) {
0278: transactionQueue[i] = transaction;
0279: // signal to the transaction processing thread, that there
0280: // is something to do
0281: continueWork();
0282: break;
0283: }
0284: }
0285:
0286: if (i == transactionQueue.length) {
0287: throw new TransactionModuleException(
0288: "No more space for " + "new transactions");
0289: }
0290: }
0291:
0292: /**
0293: * Tells the transaction processing thread to continue processing the
0294: * transactions.
0295: */
0296: public void continueWork() {
0297: synchronized (transactionQueue) {
0298: wait = false;
0299: transactionQueue.notify();
0300: }
0301: }
0302: }
0303:
0304: /**
0305: * This class represents a transaction notification thread. It notifies
0306: * the transaction listeners associated with the transactions about the
0307: * final state of the transactions. After a successful notification it
0308: * clears the transaction record's "was missed" flag.
0309: * <p>
0310: * It gets the transactions for the notification from its internal
0311: * transaction notification queue and it has methods allowing the addition
0312: * of new transaction records to this queue. Each transaction in the queue
0313: * has associated a transaction module (<code>TransactionModuleImpl</code>)
0314: * which in turn holds a reference to the transaction listener, that should
0315: * be notified.
0316: */
0317: private class TransactionNotificationThread extends Thread {
0318:
0319: /** Transaction listeners array */
0320: private Vector notificationQueue = new Vector();
0321:
0322: /** Notification thread exit flag. */
0323: private boolean finished;
0324:
0325: /** Wait for next transaction flag. */
0326: private boolean wait = true;
0327:
0328: /**
0329: * Offset of TransactionRecord object
0330: * inside notificationQueue element.
0331: */
0332: private static final int RECORD = 0;
0333:
0334: /**
0335: * Offset of TransactionModuleImpl object
0336: * inside notificationQueue element.
0337: */
0338: private static final int MODULE = 1;
0339:
0340: /**
0341: * This method is run when the thread starts.
0342: */
0343: public void run() {
0344: TransactionStore transactionStore = getTransactionStore();
0345:
0346: // listeners notification queue
0347: while (!finished) {
0348: int count = notificationQueue.size();
0349:
0350: while (count > 0) {
0351: Object[] element = (Object[]) notificationQueue
0352: .elementAt(0);
0353:
0354: // synchronized with the transaction module setListener
0355: // method => if the application invokes rhe
0356: // <code>TransactionModule.setListener</code> method with
0357: // <code>null</code>, after the call ends, there will be
0358: // no further notifications and no notification will be in
0359: // progress at that time
0360: synchronized (element[MODULE]) {
0361: TransactionRecord record = (TransactionRecord) element[RECORD];
0362: TransactionListener listener = ((TransactionModuleImpl) element[MODULE])
0363: .getListener();
0364:
0365: if (listener != null) {
0366: try {
0367: int transactionID = record
0368: .getTransactionID();
0369: // test if the record has been delivered before,
0370: // because of for example 2 successive calls to
0371: // deliverMissedTransactions
0372: if (!transactionStore
0373: .wasDelivered(transactionID)) {
0374: listener.processed(record);
0375: transactionStore
0376: .setDelivered(transactionID);
0377: }
0378: } catch (IOException e) {
0379: // failed to notify or a transaction store
0380: // failure
0381: // ignore
0382: }
0383: }
0384:
0385: notificationQueue.removeElementAt(0);
0386: --count;
0387: }
0388: }
0389:
0390: synchronized (notificationQueue) {
0391: if (wait) {
0392: try {
0393: notificationQueue.wait();
0394: } catch (InterruptedException e) {
0395: }
0396: }
0397: wait = true;
0398: }
0399: }
0400: }
0401:
0402: /**
0403: * Add the given transaction record and transaction module to the
0404: * transaction notification queue. It wakes up the transaction
0405: * notification thread if necessary.
0406: *
0407: * @param record the transaction record
0408: * @param module the transaction module
0409: */
0410: public void addTransaction(TransactionRecord record,
0411: TransactionModuleImpl module) {
0412: Object[] element = new Object[] { record, module };
0413:
0414: notificationQueue.addElement(element);
0415: continueWork();
0416: }
0417:
0418: /**
0419: * Adds the given transaction records to the transaction notification
0420: * queue. Each transaction record is associated with the given
0421: * transaction module. If the transaction notification thread is not
0422: * running at the time the method is executed, it is woken up by the
0423: * method.
0424: *
0425: * @param records an array of the transaction records
0426: * @param module the module associated with the records
0427: */
0428: public void addTransactions(TransactionRecord[] records,
0429: TransactionModuleImpl module) {
0430: Object[] element;
0431:
0432: for (int i = 0; i < records.length; ++i) {
0433: element = new Object[2];
0434: element[RECORD] = records[i];
0435: element[MODULE] = module;
0436: notificationQueue.addElement(element);
0437: }
0438:
0439: continueWork();
0440: }
0441:
0442: /**
0443: * This method makes the transaction notification thread to continue
0444: * notification.
0445: */
0446: public void continueWork() {
0447: synchronized (notificationQueue) {
0448: wait = false;
0449: notificationQueue.notify();
0450: }
0451: }
0452: }
0453:
0454: /**
0455: * Signals the transaction processing thread to continue processing of the
0456: * queued transactions. It means that there is at least one transaction in
0457: * the queue, which is not waiting for some event (an user action,
0458: * finishing of some payment adapter's thread, etc.).
0459: */
0460: final void continueProcessing() {
0461: processingThread.continueWork();
0462: }
0463:
0464: /**
0465: * Creates a new transaction from the given payment requests. It sets the
0466: * payment module as a transaction processor and adds the new transaction
0467: * to the transaction queue. Returns a generated identification number,
0468: * which identifies the new transaction.
0469: *
0470: * @param transactionModule the transaction module, which called the method
0471: * @param featureID the identifier of the feature to be paid for
0472: * @param featureTitle the title of the feature
0473: * @param featureDescription the description of the feature
0474: * @param payload the payload to be transfered as a part of the payment or
0475: * <code>null</code> if no such payload required
0476: * @return the identification number of the transaction
0477: * @throws TransactionModuleException if there is no more space to store
0478: * the new transaction
0479: */
0480: synchronized public final int addTransaction(
0481: TransactionModuleImpl transactionModule, int featureID,
0482: String featureTitle, String featureDescription,
0483: byte[] payload) throws TransactionModuleException {
0484:
0485: // execute the transaction processing thread if not running
0486: if (processingThread == null) {
0487: processingThread = new TransactionProcessingThread();
0488: processingThread.start();
0489: }
0490:
0491: TransactionStore transactionStore = getTransactionStore();
0492:
0493: Transaction transaction = new Transaction(this ,
0494: transactionModule, featureID, featureTitle,
0495: featureDescription, payload);
0496:
0497: int transactionID;
0498: try {
0499: transactionID = transactionStore.reserve(transactionModule
0500: .getApplicationID(), transaction);
0501: } catch (IOException e) {
0502: throw new TransactionModuleException("No more space for "
0503: + "transaction records");
0504: }
0505: transaction.setTransactionID(transactionID);
0506:
0507: PaymentInfo paymentInfo = getPaymentInfo(transaction);
0508: synchronized (paymentInfo) {
0509: processingThread.addTransaction(transaction);
0510:
0511: if (paymentInfo.needsUpdate()) {
0512: try {
0513: paymentInfo.wait();
0514: } catch (InterruptedException e) {
0515: // ignore
0516: }
0517:
0518: if (paymentInfo.needsUpdate()) {
0519: throw new TransactionModuleException(
0520: "The provisioning "
0521: + "information needs an update");
0522: }
0523: }
0524: }
0525:
0526: return transactionID;
0527: }
0528:
0529: /**
0530: * Adds the given transaction record to the transaction notification queue
0531: * and associates it with the given payment module. The payment module holds
0532: * a reference to the listener, which should be notified.
0533: *
0534: * @param record the transaction record
0535: * @param module the transaction module
0536: */
0537: final void addTransactionForNotification(TransactionRecord record,
0538: TransactionModuleImpl module) {
0539: // execute the transaction notification thread if not running
0540: if (notificationThread == null) {
0541: notificationThread = new TransactionNotificationThread();
0542: notificationThread.start();
0543: }
0544:
0545: notificationThread.addTransaction(record, module);
0546: }
0547:
0548: /**
0549: * Adds the given transaction records to the transaction notification
0550: * queue and associates them with the given payment module. The payment
0551: * module holds a reference to the listener, which should be notified.
0552: *
0553: * @param records an array of the transaction records
0554: * @param module the transaction module
0555: */
0556: final void addTransactionsForNotification(
0557: TransactionRecord[] records, TransactionModuleImpl module) {
0558: // execute the transaction notification thread if not running
0559: if (notificationThread == null) {
0560: notificationThread = new TransactionNotificationThread();
0561: notificationThread.start();
0562: }
0563:
0564: notificationThread.addTransactions(records, module);
0565: }
0566:
0567: /** Pointer to utility methods class. */
0568: private final Utils utilities = getUtilities();
0569:
0570: /** 'NEVER' string */
0571: private final String NEVER = utilities
0572: .getString(Utils.PAYMENT_PROV_SEL_DLG_NEVER);
0573:
0574: /**
0575: * This class represents an UI for displaying and updating of payment
0576: * information and selecting a payment provider for the payment. The
0577: * payment is represented by an instance of the <code>Transaction</code>
0578: * class.
0579: */
0580: private class PaymentModuleUI implements CommandListener,
0581: ItemCommandListener, ItemStateListener {
0582:
0583: /** A transaction which represents the payment. */
0584: private Transaction transaction;
0585:
0586: /** Reject payment command. */
0587: private final Command rejectCommand = new Command(utilities
0588: .getString(Utils.PAYMENT_PROV_SEL_DLG_NO),
0589: Command.CANCEL, 1);
0590:
0591: /** Accept payment command. */
0592: private final Command acceptCommand = new Command(utilities
0593: .getString(Utils.PAYMENT_PROV_SEL_DLG_YES), Command.OK,
0594: 1);
0595:
0596: /** Update payment info command. */
0597: private final Command updateCommand = new Command(utilities
0598: .getString(Utils.PAYMENT_PROV_SEL_DLG_UPDATE),
0599: Command.ITEM, 2);
0600:
0601: /** Cancel payment info update command. */
0602: private final Command stopCommand = new Command(utilities
0603: .getString(Utils.PAYMENT_UPDATE_DLG_STOP),
0604: Command.STOP, 1);
0605:
0606: /** Provider selection form. */
0607: private Form providerSelectionForm;
0608:
0609: /** Question string max length. */
0610: private static final int QUESTION_LENGTH = 90;
0611:
0612: /** Feature description form item. */
0613: private StringItem featureDescriptionItem;
0614:
0615: /** Payment question form item. */
0616: private StringItem paymentQuestionItem;
0617:
0618: /** Provider selection choice group. */
0619: private ChoiceGroup providerSelectionChoice;
0620:
0621: /** Payment info update date item. */
0622: private StringItem updateDateItem;
0623:
0624: /** Last update stamp item. */
0625: private StringItem updateStampItem;
0626:
0627: /** Provider identifiers array. */
0628: private int[] providers;
0629:
0630: /** Payment info update form. */
0631: private Form paymentUpdateForm;
0632:
0633: /** Payment info update progress gauge. */
0634: private Gauge progressGauge;
0635:
0636: /** Payment info update state. */
0637: private int updateState = -1;
0638:
0639: /** Flag indicates that payment info update was canceled. */
0640: private boolean cancel;
0641:
0642: /**
0643: * Creates an instance of the <code>PaymentModuleUI</code> class.
0644: * It requires a transaction which represents the payment.
0645: *
0646: * @param transaction the transaction
0647: */
0648: public PaymentModuleUI(Transaction transaction) {
0649: this .transaction = transaction;
0650: }
0651:
0652: /** Displays a payment update form for the transaction. */
0653: public void showPaymentUpdateForm() {
0654: if (paymentUpdateForm == null) {
0655: // create the form
0656: paymentUpdateForm = new Form(utilities
0657: .getString(Utils.PAYMENT_UPDATE_DLG_CAPTION));
0658:
0659: progressGauge = new Gauge(null, false,
0660: Gauge.INDEFINITE, Gauge.CONTINUOUS_RUNNING);
0661: progressGauge.setPreferredSize(paymentUpdateForm
0662: .getWidth(), -1);
0663:
0664: paymentUpdateForm.append(progressGauge);
0665:
0666: paymentUpdateForm.addCommand(stopCommand);
0667: paymentUpdateForm.setCommandListener(this );
0668: }
0669:
0670: updatePaymentUpdateForm();
0671: preemptDisplay(classSecurityToken, paymentUpdateForm);
0672: }
0673:
0674: /** Displays a provider selection form for the transaction. */
0675: public void showProviderSelectionForm() {
0676: if (providerSelectionForm == null) {
0677: // create the form
0678: providerSelectionForm = new Form(transaction
0679: .getFeatureTitle());
0680:
0681: featureDescriptionItem = new StringItem(transaction
0682: .getFeatureDescription(), null);
0683: paymentQuestionItem = new StringItem(null, null);
0684: providerSelectionChoice = new ChoiceGroup(utilities
0685: .getString(Utils.PAYMENT_PROV_SEL_DLG_PAY_BY),
0686: ChoiceGroup.POPUP);
0687: updateDateItem = new StringItem(
0688: utilities
0689: .getString(Utils.PAYMENT_PROV_SEL_DLG_UPDATE_DATE),
0690: null);
0691: updateStampItem = new StringItem(
0692: utilities
0693: .getString(Utils.PAYMENT_PROV_SEL_DLG_UPDATE_STAMP),
0694: null);
0695:
0696: featureDescriptionItem
0697: .setLayout(Item.LAYOUT_NEWLINE_BEFORE
0698: | Item.LAYOUT_NEWLINE_AFTER);
0699: paymentQuestionItem
0700: .setLayout(Item.LAYOUT_NEWLINE_BEFORE
0701: | Item.LAYOUT_NEWLINE_AFTER);
0702: providerSelectionChoice
0703: .setLayout(Item.LAYOUT_NEWLINE_BEFORE
0704: | Item.LAYOUT_NEWLINE_AFTER);
0705: updateDateItem.setLayout(Item.LAYOUT_NEWLINE_BEFORE
0706: | Item.LAYOUT_NEWLINE_AFTER);
0707: updateStampItem.setLayout(Item.LAYOUT_NEWLINE_BEFORE
0708: | Item.LAYOUT_NEWLINE_AFTER);
0709:
0710: Font defaultFont = Font.getDefaultFont();
0711: StringItem separator;
0712: int separatorHeight = defaultFont.getHeight() >>> 1;
0713: int separatorWidth = providerSelectionForm.getWidth() >>> 1;
0714:
0715: int reserveLines = defaultFont.charWidth('M')
0716: * QUESTION_LENGTH
0717: / providerSelectionForm.getWidth() + 1;
0718: paymentQuestionItem.setPreferredSize(-1, reserveLines
0719: * defaultFont.getHeight());
0720:
0721: separator = new StringItem(null, null);
0722: separator.setPreferredSize(separatorWidth,
0723: separatorHeight);
0724: separator.setLayout(Item.LAYOUT_NEWLINE_BEFORE
0725: | Item.LAYOUT_NEWLINE_AFTER);
0726: providerSelectionForm.append(featureDescriptionItem);
0727: providerSelectionForm.append(separator);
0728: providerSelectionForm.append(paymentQuestionItem);
0729: providerSelectionForm.append(providerSelectionChoice);
0730:
0731: separator = new StringItem(null, null);
0732: separator.setPreferredSize(separatorWidth,
0733: separatorHeight);
0734: separator.setLayout(Item.LAYOUT_NEWLINE_BEFORE
0735: | Item.LAYOUT_NEWLINE_AFTER);
0736: providerSelectionForm.append(separator);
0737: providerSelectionForm.append(updateDateItem);
0738: providerSelectionForm.append(updateStampItem);
0739:
0740: StringItem updateItem = new StringItem(null,
0741: updateCommand.getLabel(), Item.BUTTON);
0742:
0743: updateItem.setLayout(Item.LAYOUT_NEWLINE_BEFORE
0744: | Item.LAYOUT_RIGHT);
0745: updateItem.setDefaultCommand(updateCommand);
0746: updateItem.setItemCommandListener(this );
0747:
0748: separator = new StringItem(null, null);
0749: separator.setPreferredSize(separatorWidth,
0750: separatorHeight);
0751: separator.setLayout(Item.LAYOUT_NEWLINE_BEFORE
0752: | Item.LAYOUT_NEWLINE_AFTER);
0753: providerSelectionForm.append(separator);
0754: providerSelectionForm.append(updateItem);
0755:
0756: // reset the layout to the left
0757: StringItem lastItem = new StringItem(null, null);
0758: lastItem.setLayout(Item.LAYOUT_LEFT);
0759: providerSelectionForm.append(lastItem);
0760:
0761: providerSelectionForm.addCommand(acceptCommand);
0762: providerSelectionForm.addCommand(rejectCommand);
0763:
0764: providerSelectionForm.setCommandListener(this );
0765: providerSelectionForm.setItemStateListener(this );
0766: }
0767:
0768: PaymentInfo paymentInfo = getPaymentInfo(transaction);
0769: providers = getValidProviders(paymentInfo);
0770:
0771: // fill the provider selection choice group
0772: int oldIndex = providerSelectionChoice.getSelectedIndex();
0773: providerSelectionChoice.deleteAll();
0774: for (int i = 0; i < providers.length; ++i) {
0775: ProviderInfo providerInfo = paymentInfo
0776: .getProvider(providers[i]);
0777:
0778: PaymentAdapter adapter = null;
0779: try {
0780: adapter = getAdapter(providerInfo.getAdapter(),
0781: providerInfo.getConfiguration());
0782: } catch (PaymentException e) {
0783: }
0784:
0785: providerSelectionChoice.append(adapter.getDisplayName()
0786: + " - " + providerInfo.getName(), null);
0787: }
0788: if (oldIndex >= providers.length) {
0789: oldIndex = providers.length - 1;
0790: }
0791: if (oldIndex < 0) {
0792: oldIndex = 0;
0793: }
0794: providerSelectionChoice.setSelectedIndex(oldIndex, true);
0795:
0796: updateProviderSelectionForm();
0797: preemptDisplay(classSecurityToken, providerSelectionForm);
0798: }
0799:
0800: /**
0801: * Displays an alert with the given title and message.
0802: *
0803: * @param title the title
0804: * @param message the message
0805: */
0806: private void displayException(String title, String message) {
0807: Alert a = new Alert(title, message, null, AlertType.ERROR);
0808:
0809: a.setTimeout(Alert.FOREVER);
0810: a.setCommandListener(this );
0811:
0812: preemptDisplay(classSecurityToken, a);
0813: }
0814:
0815: /**
0816: * Updates the user interface of the provider selection form to reflect
0817: * changes made by the user.
0818: */
0819: private void updateProviderSelectionForm() {
0820: int providerID = providers[providerSelectionChoice
0821: .getSelectedIndex()];
0822:
0823: PaymentInfo paymentInfo = getPaymentInfo(transaction);
0824: int priceTag = paymentInfo
0825: .getPriceTagForFeature(transaction.getFeatureID());
0826:
0827: updateDateItem
0828: .setText((paymentInfo.getUpdateDate() == null) ? NEVER
0829: : paymentInfo.getUpdateDate().toString());
0830: updateStampItem.setText(paymentInfo.getUpdateStamp()
0831: .toString());
0832:
0833: ProviderInfo providerInfo = paymentInfo
0834: .getProvider(providerID);
0835: PaymentAdapter adapter = null;
0836: try {
0837: adapter = getAdapter(providerInfo.getAdapter(),
0838: providerInfo.getConfiguration());
0839: } catch (PaymentException e) {
0840: }
0841:
0842: String question = adapter.getPaymentQuestion(providerInfo
0843: .getName(), providerInfo.getPrice(priceTag),
0844: providerInfo.getCurrency());
0845:
0846: paymentQuestionItem.setText(question);
0847: }
0848:
0849: /**
0850: * Updates the payment update form to reflect the state the payment
0851: * update is in.
0852: */
0853: private void updatePaymentUpdateForm() {
0854: int key;
0855: int retry = 0;
0856:
0857: if (updateState == -1) {
0858: progressGauge.setLabel(null);
0859: return;
0860: }
0861:
0862: if (cancel) {
0863: progressGauge
0864: .setLabel(utilities
0865: .getString(Utils.PAYMENT_UPDATE_DLG_CANCELLING));
0866: return;
0867: }
0868:
0869: if (updateState < STATE_DOWNLOADING) {
0870: retry = updateState >>> RETRY_SHIFT;
0871: updateState &= (1 << RETRY_SHIFT) - 1;
0872: }
0873:
0874: switch (updateState) {
0875: case STATE_CONNECTING:
0876: key = Utils.PAYMENT_UPDATE_DLG_CONNECTING;
0877: break;
0878: case STATE_SENDING_REQUEST:
0879: key = Utils.PAYMENT_UPDATE_DLG_SENDING;
0880: break;
0881: case STATE_RETRY_WAITING:
0882: key = Utils.PAYMENT_UPDATE_DLG_WAITING;
0883: break;
0884: case STATE_DOWNLOADING:
0885: key = Utils.PAYMENT_UPDATE_DLG_DOWNLOADING;
0886: break;
0887: case STATE_VERIFYING:
0888: key = Utils.PAYMENT_UPDATE_DLG_VERIFYING;
0889: break;
0890: default:
0891: return;
0892: }
0893:
0894: String message = utilities.getString(key);
0895:
0896: if (retry > 0) {
0897: String[] params = { Integer.toString(retry),
0898: Integer.toString(MAX_RETRY_COUNT) };
0899:
0900: message += "\n"
0901: + utilities.getString(
0902: Utils.PAYMENT_UPDATE_DLG_RETRY, params);
0903: }
0904:
0905: progressGauge.setLabel(message);
0906: }
0907:
0908: /** Time of payment update form last update. */
0909: private long lastUIUpdate;
0910:
0911: /**
0912: * A method which is called by the payment module when the state of the
0913: * payment update changes.
0914: *
0915: * @param newState the new state of the payment update
0916: * @throws InterruptedException if the payment update has been
0917: * interrupted by the user
0918: */
0919: public void notifyStateChange(int newState)
0920: throws InterruptedException {
0921: if (cancel) {
0922: throw new InterruptedException("stopped");
0923: }
0924: if (updateState != newState) {
0925: long sleepTime = lastUIUpdate + ALERT_TIMEOUT
0926: - System.currentTimeMillis();
0927:
0928: if (sleepTime > 0) {
0929: Thread.sleep(sleepTime);
0930: }
0931:
0932: updateState = newState;
0933: updatePaymentUpdateForm();
0934:
0935: lastUIUpdate = System.currentTimeMillis();
0936: }
0937: }
0938:
0939: /**
0940: * Implements a response to user actions.
0941: *
0942: * @param c the executed command
0943: * @param d the <code>Displayable</code> on which the command has
0944: * been executed
0945: */
0946: public void commandAction(Command c, Displayable d) {
0947: if (c == acceptCommand) {
0948: preemptDisplay(classSecurityToken, null);
0949: currentUI = null;
0950:
0951: assignTransaction(transaction,
0952: providers[providerSelectionChoice
0953: .getSelectedIndex()]);
0954: transaction.setWaiting(false);
0955: } else if (c == rejectCommand) {
0956: preemptDisplay(classSecurityToken, null);
0957: currentUI = null;
0958:
0959: // reject the transaction
0960: transaction.setState(Transaction.REJECTED);
0961: transaction.setNeedsUI(false);
0962: transaction.setWaiting(false);
0963: } else if (c == Alert.DISMISS_COMMAND) {
0964: preemptDisplay(classSecurityToken, null);
0965: PaymentInfo paymentInfo = getPaymentInfo(transaction);
0966: if (paymentInfo.needsUpdate()) {
0967: // we failed to update and the current information can't
0968: // be used => fail the transaction
0969: currentUI = null;
0970:
0971: // discard the transaction
0972: transaction.setState(Transaction.DISCARDED);
0973: transaction.setNeedsUI(false);
0974: transaction.setWaiting(false);
0975: } else {
0976: transaction.setState(Transaction.ENTERED);
0977: transaction.setWaiting(false);
0978: // showProviderSelectionForm();
0979: }
0980:
0981: // release the process method if waiting for an update
0982: synchronized (paymentInfo) {
0983: paymentInfo.notifyAll();
0984: }
0985: } else if (c == stopCommand) {
0986: if (!cancel) {
0987: cancel = true;
0988: updatePaymentUpdateForm();
0989: // processingThread.interrupt();
0990: }
0991: }
0992: }
0993:
0994: /**
0995: * Implements a response to user actions.
0996: *
0997: * @param c the executed command
0998: * @param item the item associated with the executed command
0999: */
1000: public void commandAction(Command c, Item item) {
1001: if (c == updateCommand) {
1002: preemptDisplay(classSecurityToken, null);
1003: updateState = -1;
1004: cancel = false;
1005: transaction.setState(Transaction.UPDATE);
1006: transaction.setWaiting(false);
1007: }
1008: }
1009:
1010: /**
1011: * Called when internal state of an Item has been changed by the user.
1012: *
1013: * @param item the item that was changed
1014: */
1015: public void itemStateChanged(Item item) {
1016: if (item == providerSelectionChoice) {
1017: updateProviderSelectionForm();
1018: }
1019: }
1020: }
1021:
1022: /**
1023: * Assigns the given transaction to the provider identified by its
1024: * identification number. It sets the transaction processor of the
1025: * transaction to the provider's payment adapter.
1026: *
1027: * @param transaction the transaction
1028: * @param providerID the provider id
1029: */
1030: protected void assignTransaction(Transaction transaction,
1031: int providerID) {
1032: PaymentInfo paymentInfo = getPaymentInfo(transaction);
1033: int priceTag = paymentInfo.getPriceTagForFeature(transaction
1034: .getFeatureID());
1035:
1036: ProviderInfo providerInfo = paymentInfo.getProvider(providerID);
1037: // get the adapter instance for the given provider
1038: PaymentAdapter adapter = null;
1039: try {
1040: adapter = getAdapter(providerInfo.getAdapter(),
1041: providerInfo.getConfiguration());
1042: } catch (PaymentException e) {
1043: }
1044:
1045: // fill the transaction fields with the provider specific values
1046: transaction.setProviderName(providerInfo.getName());
1047: transaction.setCurrency(providerInfo.getCurrency());
1048: transaction.setPrice(providerInfo.getPrice(priceTag));
1049: transaction.setSpecificPriceInfo(providerInfo
1050: .getPaySpecificPriceInfo(priceTag));
1051:
1052: // === DEBUG MODE ===
1053: // let a subclass to handle the transaction in the debug mode, if it
1054: // does, don't forward the control over the transaction to the payment
1055: // adapter
1056: if (handleTransactionDebugMode(transaction)) {
1057: return;
1058: }
1059: // === DEBUG MODE ===
1060:
1061: // set the adapter to be a transaction processor for the transaction
1062: transaction.setTransactionProcessor(adapter);
1063:
1064: // update the state of the transaction
1065: transaction.setState(Transaction.ASSIGNED);
1066: }
1067:
1068: /**
1069: * Returns an array of provider identifiers, which could be used to pay
1070: * for features.
1071: *
1072: * @param paymentInfo the payment information for the MIDlet which
1073: * initiated the payment
1074: * @return the array of provider identifiers
1075: */
1076: protected final int[] getValidProviders(PaymentInfo paymentInfo) {
1077: int numProviders = paymentInfo.getNumProviders();
1078: int numAccepted = 0;
1079: boolean[] accepted = new boolean[numProviders];
1080:
1081: for (int i = 0; i < numProviders; ++i) {
1082: accepted[i] = false;
1083:
1084: ProviderInfo providerInfo = paymentInfo.getProvider(i);
1085:
1086: PaymentAdapter adapter = null;
1087: try {
1088: adapter = getAdapter(providerInfo.getAdapter(),
1089: providerInfo.getConfiguration());
1090: } catch (PaymentException e) {
1091: }
1092: if (adapter == null) {
1093: continue;
1094: }
1095:
1096: accepted[i] = true;
1097: ++numAccepted;
1098: }
1099:
1100: int[] providers = new int[numAccepted];
1101: for (int i = 0, j = 0; i < numProviders; ++i) {
1102: if (accepted[i]) {
1103: providers[j++] = i;
1104: }
1105: }
1106:
1107: return providers;
1108: }
1109:
1110: /** Array of created payment adapters. */
1111: private Hashtable paymentAdapters = new Hashtable();
1112:
1113: /**
1114: * Indicates if the given adapter is supported by the device.
1115: *
1116: * @param name the name of the adapter
1117: * @return <code>true</code> if the given adapter is supported
1118: */
1119: public boolean isSupportedAdapter(String name) {
1120: if ("PPSMS".equals(name)) {
1121: return true;
1122: }
1123: return false;
1124: }
1125:
1126: /**
1127: * Creates the payment adapter for the given registered adapter name and
1128: * the adapter configuration string. It returns <code>null</code> if no
1129: * such adapter can be created.
1130: *
1131: * @param adapter the registered adapter name
1132: * @param configuration the adapter configuration string
1133: * @return the instance of the payment adapter or <code>null</code>
1134: * @throws PaymentException if the adapter configuration string
1135: * has an invalid format
1136: */
1137: protected PaymentAdapter createAdapter(String adapter,
1138: String configuration) throws PaymentException {
1139: if ("PPSMS".equals(adapter)) {
1140: if (configuration.indexOf(',') != -1) {
1141: String mcc = configuration.substring(0, configuration
1142: .indexOf(','));
1143: String mnc = configuration.substring(configuration
1144: .indexOf(',') + 1);
1145:
1146: if ((mcc != null) && (mnc != null)) {
1147: mcc = mcc.trim();
1148: mnc = mnc.trim();
1149:
1150: try {
1151: Integer.parseInt(mcc);
1152: Integer.parseInt(mnc);
1153: } catch (NumberFormatException nfe) {
1154: throw new PaymentException(
1155: PaymentException.INVALID_ADAPTER_CONFIGURATION,
1156: configuration);
1157: }
1158:
1159: if ((mcc.length() != 3) || (mnc.length() < 2)
1160: || (mnc.length() > 3)) {
1161: throw new PaymentException(
1162: PaymentException.INVALID_ADAPTER_CONFIGURATION,
1163: configuration);
1164: }
1165:
1166: // get system property
1167: String MCC = System.getProperty("MCC");
1168: // get jsr default property
1169: if (null == MCC) {
1170: MCC = System.getProperty("payment.mcc");
1171: }
1172: // get system property
1173: String MNC = System.getProperty("MNC");
1174: // get jsr default property
1175: if (null == MNC) {
1176: MNC = System.getProperty("payment.mnc");
1177: }
1178:
1179: if (mcc.equals(MCC) && mnc.equals(MNC)) {
1180: return PPSMSAdapter.getInstance(configuration);
1181: } else {
1182: return null;
1183: }
1184: } else {
1185: throw new PaymentException(
1186: PaymentException.INVALID_ADAPTER_CONFIGURATION,
1187: configuration);
1188: }
1189: } else {
1190: throw new PaymentException(
1191: PaymentException.INVALID_ADAPTER_CONFIGURATION,
1192: configuration);
1193: }
1194: }
1195: return null;
1196: }
1197:
1198: /**
1199: * Returns the payment adapter for the given registered adapter name and
1200: * the adapter configuration string. It either returns an adapter created
1201: * before for the given combination of <code>name</code> and
1202: * <code>providerString</code> or creates a new instance if the old one
1203: * doesn't exist. It returns <code>null</code> if no adapter of the given
1204: * parameters can be created.
1205: *
1206: * @param name the registered adapter name
1207: * @param configuration the adapter configuration string
1208: * @return the instance of the payment adapter or <code>null</code>
1209: * @throws PaymentException if the adapter configuration string
1210: * has an invalid format
1211: */
1212: PaymentAdapter getAdapter(String name, String configuration)
1213: throws PaymentException {
1214: String adapterLookupString = name + "#"
1215: + normalizeConfigurationString(configuration);
1216: PaymentAdapter adapter = (PaymentAdapter) paymentAdapters
1217: .get(adapterLookupString);
1218: if (adapter == null) {
1219: adapter = createAdapter(name, configuration);
1220: if (adapter != null) {
1221: paymentAdapters.put(adapterLookupString, adapter);
1222: }
1223: }
1224: return adapter;
1225: }
1226:
1227: /**
1228: * Replaces the current <code>Displayable</code> with the new one if the
1229: * <code>nextDisplayable</code> is not <code>null</code> or it recovers
1230: * the previous <code>Displayable</code> if the <code>nextDisplayable</code>
1231: * is <code>null</code>.
1232: *
1233: * @param token a security token, which allows preempting
1234: * @param nextDisplayable the <code>Displayable</code> to show or
1235: * <code>null</code> if the recovery of the old
1236: * <code>Displayable</code> is requested
1237: */
1238: protected abstract void preemptDisplay(SecurityToken token,
1239: Displayable nextDisplayable);
1240:
1241: /** Current payment module UI. */
1242: private PaymentModuleUI currentUI;
1243:
1244: /**
1245: * A method which is responsible for processing of transactions which are
1246: * not yet assigned to the provider specific adapters or they are finished
1247: * and need to be removed from the transaction processing queue.
1248: *
1249: * @param transaction the transaction to be processed
1250: * @return the processed transaction or <code>null</code> if the transaction
1251: * should be removed from the transaction queue
1252: */
1253: public Transaction process(Transaction transaction) {
1254: PaymentInfo paymentInfo;
1255: boolean needsUpdate;
1256:
1257: switch (transaction.getState()) {
1258: case Transaction.ENTERED:
1259: paymentInfo = getPaymentInfo(transaction);
1260: needsUpdate = paymentInfo.needsUpdate();
1261: // === DEBUG MODE ===
1262: if (!needsUpdate && handleAutoRequestMode(transaction)) {
1263: break;
1264: }
1265: // === DEBUG MODE ===
1266: currentUI = new PaymentModuleUI(transaction);
1267: if (needsUpdate) {
1268: transaction.setState(Transaction.UPDATE);
1269: } else {
1270: transaction.setWaiting(true);
1271: currentUI.showProviderSelectionForm();
1272: }
1273: break;
1274:
1275: case Transaction.UPDATE:
1276: // currentUI != null
1277: paymentInfo = getPaymentInfo(transaction);
1278:
1279: // validate the http or https connection now (before we preempt
1280: // the display)
1281: try {
1282: String name = paymentInfo.getUpdateURL();
1283: int permission = name.startsWith("https") ? Permissions.HTTPS
1284: : Permissions.HTTP;
1285: int colon = name.indexOf(':');
1286: if (colon != -1) {
1287: if (colon < name.length() - 1) {
1288: name = name.substring(colon + 1);
1289: } else {
1290: name = "";
1291: }
1292: }
1293: transaction.getTransactionModule().checkForPermission(
1294: permission, name);
1295: } catch (InterruptedException e) {
1296: // ignore, let the download fail
1297: }
1298:
1299: transaction.setWaiting(true);
1300: currentUI.showPaymentUpdateForm();
1301:
1302: Exception ex = null;
1303: try {
1304: synchronized (paymentInfo) {
1305: updatePaymentInfo(paymentInfo);
1306: }
1307:
1308: if (paymentInfo.cache()) {
1309: savePaymentInfo(transaction);
1310: }
1311:
1312: preemptDisplay(classSecurityToken, null);
1313: // === DEBUG MODE ===
1314: if (handleAutoRequestMode(transaction)) {
1315: transaction.setWaiting(false);
1316: currentUI = null;
1317: break;
1318: }
1319: // === DEBUG MODE ===
1320: currentUI.showProviderSelectionForm();
1321: } catch (InterruptedException e) {
1322: preemptDisplay(classSecurityToken, null);
1323: if (paymentInfo.needsUpdate()) {
1324: currentUI = null;
1325:
1326: // discard the transaction
1327: transaction.setState(Transaction.DISCARDED);
1328: transaction.setNeedsUI(false);
1329: transaction.setWaiting(false);
1330: } else {
1331: currentUI.showProviderSelectionForm();
1332: }
1333: } catch (PaymentException pe) {
1334: ex = pe;
1335: } catch (IOException ioe) {
1336: ex = ioe;
1337: }
1338: if (ex != null) {
1339: preemptDisplay(classSecurityToken, null);
1340: currentUI.displayException(utilities
1341: .getString(Utils.PAYMENT_ERROR_DLG_CAPTION),
1342: getErrorMessage(ex));
1343:
1344: // don't release the process method if waiting for an
1345: // update yet
1346: break;
1347: }
1348:
1349: // release the process method if waiting for an update
1350: synchronized (paymentInfo) {
1351: paymentInfo.notifyAll();
1352: }
1353:
1354: break;
1355:
1356: case Transaction.REJECTED:
1357: case Transaction.SUCCESSFUL:
1358: case Transaction.FAILED:
1359: TransactionStore transactionStore = getTransactionStore();
1360:
1361: try {
1362: // create a transaction record for the transaction and add
1363: // it to the transaction notification queue
1364: TransactionRecord transactionRecord = transactionStore
1365: .addTransaction(transaction);
1366: addTransactionForNotification(transactionRecord,
1367: transaction.getTransactionModule());
1368: } catch (IOException e) {
1369: }
1370:
1371: // fall through
1372:
1373: case Transaction.DISCARDED:
1374: return null;
1375: }
1376:
1377: return transaction;
1378: }
1379:
1380: /** Number of payment update file download attempts. */
1381: private static final int MAX_RETRY_COUNT = 3;
1382:
1383: /** UI update constant. */
1384: private static final int RETRY_SHIFT = 2;
1385:
1386: /** Payment info update stage. */
1387: private static final int STATE_CONNECTING = 0;
1388: /** Payment info update stage. */
1389: private static final int STATE_SENDING_REQUEST = 1;
1390: /** Payment info update stage. */
1391: private static final int STATE_RETRY_WAITING = 2;
1392: /** Payment info update stage. */
1393: private static final int STATE_DOWNLOADING = 0x100;
1394: /** Payment info update stage. */
1395: private static final int STATE_VERIFYING = 0x101;
1396: /** Payment info update stage. */
1397: private static final int STATE_FINISHED = 0x200;
1398:
1399: /** Index of MIME type object inside content type array. */
1400: private static final int MIME_TYPE = 0;
1401:
1402: /** Index of CHARSET type object inside content type array. */
1403: private static final int CHARSET = 1;
1404:
1405: /** Maximum size of payment update file. */
1406: private static final int TRANSFER_CHUNK = 1024;
1407:
1408: /** Payment info update file MIME type. */
1409: private static final String UPDATE_MIME_TYPE = "text/vnd.sun.pay.provision";
1410:
1411: /**
1412: * Returns an error message for the given exception. It handles exceptions
1413: * thrown during a payment update.
1414: *
1415: * @param e the exception
1416: * @return the error message
1417: */
1418: private String getErrorMessage(Exception e) {
1419: int prefixKey = Utils.PAYMENT_ERROR_PREFIX;
1420: int suffixKey = Utils.PAYMENT_ERROR_SUFFIX;
1421: int key = -1;
1422: if (e instanceof SecurityException) {
1423: key = Utils.PAYMENT_ERROR_PERMISSIONS;
1424: suffixKey = -1;
1425: } else if (e instanceof IOException) {
1426: key = Utils.PAYMENT_ERROR_DOWNLOAD_FAILED;
1427: suffixKey = -1;
1428: } else if (e instanceof PaymentException) {
1429: PaymentException pe = (PaymentException) e;
1430:
1431: switch (pe.getReason()) {
1432: case PaymentException.UNSUPPORTED_PAYMENT_INFO:
1433: case PaymentException.UNSUPPORTED_ADAPTERS:
1434: case PaymentException.UNSUPPORTED_PROVIDERS:
1435: case PaymentException.UNSUPPORTED_URL_SCHEME:
1436: case PaymentException.UNSUPPORTED_UPDATE_CHARSET:
1437: key = Utils.PAYMENT_ERROR_UPDATE_NOT_SUPPORTED;
1438: break;
1439: case PaymentException.INFORMATION_NOT_YET_VALID:
1440: key = Utils.PAYMENT_ERROR_UPDATE_NOT_YET_VALID;
1441: break;
1442: case PaymentException.INFORMATION_EXPIRED:
1443: key = Utils.PAYMENT_ERROR_UPDATE_EXPIRED;
1444: break;
1445: case PaymentException.MISSING_MANDATORY_ATTRIBUTE:
1446: case PaymentException.INVALID_ATTRIBUTE_VALUE:
1447: case PaymentException.INVALID_ADAPTER_CONFIGURATION:
1448: case PaymentException.INVALID_PRICE_INFORMATION:
1449: case PaymentException.INVALID_PROPERTIES_FORMAT:
1450: key = Utils.PAYMENT_ERROR_UPDATE_INVALID;
1451: break;
1452: case PaymentException.INCOMPLETE_INFORMATION:
1453: key = Utils.PAYMENT_ERROR_UPDATE_INCOMPLETE;
1454: break;
1455: case PaymentException.UPDATE_SERVER_NOT_FOUND:
1456: case PaymentException.UPDATE_NOT_FOUND:
1457: case PaymentException.INVALID_UPDATE_URL:
1458: key = Utils.PAYMENT_ERROR_UPDATE_NOT_FOUND;
1459: break;
1460: case PaymentException.UPDATE_SERVER_BUSY:
1461: case PaymentException.UPDATE_REQUEST_ERROR:
1462: key = Utils.PAYMENT_ERROR_CONNECTION_FAILED;
1463: break;
1464: case PaymentException.INVALID_UPDATE_TYPE:
1465: key = Utils.PAYMENT_ERROR_UPDATE_INVALID_TYPE;
1466: break;
1467: case PaymentException.EXPIRED_PROVIDER_CERT:
1468: key = Utils.PAYMENT_ERROR_CERTIFICATE_EXPIRED;
1469: break;
1470: case PaymentException.INVALID_PROVIDER_CERT:
1471: key = Utils.PAYMENT_ERROR_CERTIFICATE_INCORRECT;
1472: break;
1473: case PaymentException.EXPIRED_CA_CERT:
1474: case PaymentException.NO_TRUSTED_CHAIN:
1475: key = Utils.PAYMENT_ERROR_CERTIFICATE_UNTRUSTED;
1476: break;
1477: case PaymentException.SIGNATURE_VERIFICATION_FAILED:
1478: key = Utils.PAYMENT_ERROR_VERIFICATION_FAILED;
1479: break;
1480: }
1481: }
1482:
1483: StringBuffer buffer = new StringBuffer(utilities
1484: .getString(prefixKey));
1485:
1486: if (key != -1) {
1487: buffer.append(" ");
1488: buffer.append(utilities.getString(key));
1489: if (suffixKey != -1) {
1490: buffer.append(" ");
1491: buffer.append(utilities.getString(suffixKey));
1492: }
1493: }
1494:
1495: return buffer.toString();
1496: }
1497:
1498: /**
1499: * Creates a http or https connection with the payment update server. After
1500: * opening the connection it sends the http request for the update file and
1501: * checks the reply. If everything is correct it returns the connection
1502: * which can be used to get the update file.
1503: *
1504: * @param url the URL of the payment update file
1505: * @return the opened connection
1506: * @throws PaymentException indicates failure
1507: * @throws IOException indicates failure
1508: * @throws InterruptedException indicates that the thread has been
1509: * interrupted while waiting for the server
1510: */
1511: private HttpConnection createConnection(String url)
1512: throws PaymentException, IOException, InterruptedException {
1513: HttpConnection httpConnection = null;
1514: int responseCode = -1;
1515:
1516: try {
1517: int retry = 0;
1518: do {
1519: currentUI.notifyStateChange(STATE_CONNECTING
1520: + (retry << RETRY_SHIFT));
1521:
1522: Connection connection;
1523: try {
1524: connection = Connector.open(url);
1525: } catch (IllegalArgumentException e) {
1526: throw new PaymentException(
1527: PaymentException.INVALID_UPDATE_URL, url,
1528: null);
1529: } catch (ConnectionNotFoundException e) {
1530: throw new PaymentException(
1531: PaymentException.INVALID_UPDATE_URL, url,
1532: null);
1533: }
1534:
1535: if (!(connection instanceof HttpConnection)) {
1536: connection.close();
1537: throw new PaymentException(
1538: PaymentException.INVALID_UPDATE_URL, url,
1539: null);
1540: }
1541:
1542: httpConnection = (HttpConnection) connection;
1543:
1544: // set User-Agent
1545: String prof = System
1546: .getProperty("microedition.profiles");
1547: int space = prof.indexOf(' ');
1548: if (space != -1) {
1549: prof = prof.substring(0, space);
1550: }
1551: httpConnection
1552: .setRequestProperty(
1553: "User-Agent",
1554: "Profile/"
1555: + prof
1556: + " Configuration/"
1557: + System
1558: .getProperty("microedition.configuration"));
1559:
1560: // set Accept-Charset
1561: httpConnection
1562: .setRequestProperty(
1563: "Accept-Charset",
1564: "UTF-8, "
1565: + System
1566: .getProperty("microedition.encoding"));
1567:
1568: // set Accept-Language
1569: String locale = System
1570: .getProperty("microedition.locale");
1571: if (locale != null) {
1572: httpConnection.setRequestProperty(
1573: "Accept-Language", locale);
1574: }
1575:
1576: currentUI.notifyStateChange(STATE_SENDING_REQUEST
1577: + (retry << RETRY_SHIFT));
1578:
1579: try {
1580: responseCode = httpConnection.getResponseCode();
1581: } catch (IOException e) {
1582: if (httpConnection.getHost() == null) {
1583: throw new PaymentException(
1584: PaymentException.INVALID_UPDATE_URL,
1585: url, null);
1586: }
1587:
1588: throw new PaymentException(
1589: PaymentException.UPDATE_SERVER_NOT_FOUND,
1590: url, null);
1591: }
1592:
1593: if ((responseCode != HttpConnection.HTTP_UNAVAILABLE)
1594: || (++retry > MAX_RETRY_COUNT)) {
1595: break;
1596: }
1597:
1598: long sleepTime = 10000;
1599:
1600: String value = httpConnection
1601: .getHeaderField("Retry-After");
1602: // parse the Retry-After field
1603: if (value != null) {
1604: try {
1605: sleepTime = Integer.parseInt(value) * 1000;
1606: } catch (NumberFormatException ne) {
1607: // not a number
1608: try {
1609: sleepTime = DateParser.parse(value);
1610: sleepTime -= System.currentTimeMillis();
1611: } catch (IllegalArgumentException de) {
1612: }
1613: }
1614: }
1615:
1616: httpConnection.close();
1617: httpConnection = null;
1618:
1619: if (sleepTime < 0) {
1620: sleepTime = 10000;
1621: } else if (sleepTime > 60000) {
1622: sleepTime = 60000;
1623: }
1624:
1625: currentUI.notifyStateChange(STATE_RETRY_WAITING
1626: + (retry << RETRY_SHIFT));
1627:
1628: Thread.sleep(sleepTime);
1629:
1630: } while (true);
1631:
1632: switch (responseCode) {
1633: case HttpConnection.HTTP_OK:
1634: break;
1635: case HttpConnection.HTTP_NOT_FOUND:
1636: throw new PaymentException(
1637: PaymentException.UPDATE_NOT_FOUND, url, null);
1638: case HttpConnection.HTTP_UNAVAILABLE:
1639: throw new PaymentException(
1640: PaymentException.UPDATE_SERVER_BUSY, url, null);
1641: default:
1642: throw new PaymentException(
1643: PaymentException.UPDATE_REQUEST_ERROR, Integer
1644: .toString(responseCode), null);
1645: }
1646:
1647: } catch (PaymentException e) {
1648: if (httpConnection != null) {
1649: httpConnection.close();
1650: }
1651: // rethrow
1652: throw e;
1653: } catch (IOException e) {
1654: if (httpConnection != null) {
1655: httpConnection.close();
1656: }
1657: // rethrow
1658: throw e;
1659: }
1660:
1661: return httpConnection;
1662: }
1663:
1664: /**
1665: * Updates the given payment information from the update URL. If an
1666: * exception is thrown during the update, the payment information is not
1667: * changed.
1668: *
1669: * @param paymentInfo the payment information
1670: * @throws PaymentException indicates failure
1671: * @throws IOException indicates failure
1672: * @throws InterruptedException if the update has been stopped by the user
1673: */
1674: private void updatePaymentInfo(PaymentInfo paymentInfo)
1675: throws PaymentException, IOException, InterruptedException {
1676: String url = paymentInfo.getUpdateURL();
1677:
1678: // 1. CONNECT TO THE SERVER AND SEND REQUEST
1679: HttpConnection httpConnection = createConnection(url);
1680:
1681: String[] contentType = { null, null };
1682: parseContentType(contentType, httpConnection.getType());
1683:
1684: // check the mime type
1685: if (!UPDATE_MIME_TYPE.equals(contentType[MIME_TYPE])) {
1686: httpConnection.close();
1687: throw new PaymentException(
1688: PaymentException.INVALID_UPDATE_TYPE,
1689: contentType[MIME_TYPE], null);
1690: }
1691:
1692: // 2. DOWNLOAD THE UPDATE FILE
1693: byte[] data = null;
1694:
1695: InputStream is;
1696: try {
1697: currentUI.notifyStateChange(STATE_DOWNLOADING);
1698:
1699: is = httpConnection.openDataInputStream();
1700: try {
1701: ByteArrayOutputStream os = new ByteArrayOutputStream(
1702: TRANSFER_CHUNK);
1703: byte[] buffer = new byte[TRANSFER_CHUNK];
1704: int read;
1705: while ((read = is.read(buffer, 0, TRANSFER_CHUNK)) > 0) {
1706: os.write(buffer, 0, read);
1707: }
1708:
1709: // not necessary
1710: os.flush();
1711:
1712: data = os.toByteArray();
1713: os.close();
1714:
1715: } finally {
1716: is.close();
1717: }
1718: } finally {
1719: httpConnection.close();
1720: }
1721:
1722: // 3. VERIFY AND UPDATE PAYMENT INFORMATION
1723: currentUI.notifyStateChange(STATE_VERIFYING);
1724: paymentInfo.updatePaymentInfo(data, contentType[CHARSET]);
1725:
1726: currentUI.notifyStateChange(STATE_FINISHED);
1727: }
1728:
1729: /**
1730: * Returns the end index of the first substring of the given string which
1731: * matches the given mask or <code>-1</code> if no such substring can be
1732: * found. The mask is given as a character array and it has only one
1733: * special character '+', which represents 0..* number of space characters.
1734: *
1735: * @param string the string
1736: * @param mask the mask
1737: * @return the index or <code>-1</code>
1738: */
1739: private static int findEndOf(String string, char[] mask) {
1740: char[] data = string.toCharArray();
1741:
1742: int i = 0; // index into data
1743: int j = 0; // index into mask
1744: int k = 0; // saved index
1745:
1746: while ((i < data.length) && (j < mask.length)) {
1747: if (mask[j] == '+') {
1748: if (data[i] <= ' ') {
1749: ++i;
1750: } else {
1751: ++j;
1752: }
1753: continue;
1754: }
1755: if (data[i] == mask[j]) {
1756: ++i;
1757: ++j;
1758: continue;
1759: }
1760: i = k + 1;
1761: j = 0;
1762: k = i;
1763: }
1764:
1765: for (; (j < mask.length) && (mask[j] == '+'); ++j) {
1766: }
1767:
1768: return (j == mask.length) ? i : -1;
1769: }
1770:
1771: /** Mask for content type searching. */
1772: private static final char[] CHARSET_MASK = { ';', '+', 'c', 'h',
1773: 'a', 'r', 's', 'e', 't', '+', '=' };
1774:
1775: /**
1776: * Extracts the MIME type and the character set from the given Content-Type
1777: * value. It returns the extracted values in the given string array. They
1778: * are stored under the <code>MIME_TYPE</code> and <code>CHARSET</code>
1779: * indexes.
1780: *
1781: * @param values the array for extracted values
1782: * @param contentType the Content-Type value
1783: */
1784: private static void parseContentType(String[] values,
1785: String contentType) {
1786: int index;
1787: String mimeType = null;
1788: String charset = "ISO-8859-1"; // the default charset
1789:
1790: // decode mimeType and charset
1791: if (contentType != null) {
1792: index = contentType.indexOf(';');
1793: mimeType = contentType;
1794: if (index != -1) {
1795: mimeType = contentType.substring(0, index);
1796: index = findEndOf(contentType, CHARSET_MASK);
1797: if (index != -1) {
1798: int index2 = contentType.indexOf(';', index);
1799: if (index2 == -1) {
1800: index2 = contentType.length();
1801: }
1802: charset = contentType.substring(index, index2);
1803: charset = charset.trim().toUpperCase();
1804: }
1805: }
1806: mimeType = mimeType.trim().toLowerCase();
1807: }
1808:
1809: values[MIME_TYPE] = mimeType;
1810: values[CHARSET] = charset;
1811: }
1812:
1813: /**
1814: * Normalizes the given configuration strings. It removes all white spaces
1815: * before and after any comma in the string.
1816: *
1817: * @param configuration the input string
1818: * @return the normalized string
1819: */
1820: private static String normalizeConfigurationString(
1821: String configuration) {
1822: StringBuffer reconstructed = new StringBuffer();
1823: Vector elements = Util.getCommaSeparatedValues(configuration);
1824:
1825: int count = elements.size();
1826: if (count > 0) {
1827: reconstructed.append((String) elements.elementAt(0));
1828: for (int i = 1; i < count; ++i) {
1829: reconstructed.append(",");
1830: reconstructed.append((String) elements.elementAt(i));
1831: }
1832: }
1833:
1834: return reconstructed.toString();
1835: }
1836:
1837: // === DEBUG MODE ===
1838: /**
1839: * Handles the success/failure/random debug mode for the given transaction.
1840: * It's called from the parts of the <code>PaymentModule</code> code where
1841: * this mode can be applied. If the debug mode is in effect the transaction
1842: * state is set accordingly and the method returns <code>true</code>.
1843: *
1844: * @param transaction the transaction
1845: * @return <code>true</code> if the transaction is handled in the method
1846: */
1847: protected boolean handleTransactionDebugMode(Transaction transaction) {
1848: return false;
1849: }
1850:
1851: /**
1852: * Handles the auto request debug mode for the given transaction. It's
1853: * called from the parts of the <code>PaymentModule</code> code where this
1854: * mode can be applied. If the auto request mode is in effect the
1855: * transaction state is set accordingly and the method returns
1856: * <code>true</code>.
1857: *
1858: * @param transaction the transaction
1859: * @return <code>true</code> if the transaction is handled in the method
1860: * (= the auto request mode is in effect)
1861: */
1862: protected boolean handleAutoRequestMode(Transaction transaction) {
1863: return false;
1864: }
1865:
1866: // === DEBUG MODE ===
1867:
1868: static {
1869: /* Hand out security token */
1870: PPSMSAdapter.initSecurityToken(classSecurityToken);
1871: }
1872: }
|