/*
*
*
* Copyright 1990-2009 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 java.util.Random;
import javax.microedition.payment.*;
import com.sun.midp.rms.*;
import javax.microedition.rms.*;
import com.sun.midp.security.*;
import com.sun.midp.util.DateParser;
import com.sun.midp.io.Util;
import java.io.*;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.lcdui.*;
import javax.microedition.io.Connector;
import javax.microedition.io.Connection;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.ConnectionNotFoundException;
import com.sun.midp.lcdui.*;
import com.sun.midp.midlet.*;
import com.sun.midp.midletsuite.*;
import com.sun.midp.i18n.Resource;
import com.sun.midp.i18n.ResourceConstants;
import com.sun.midp.log.Logging;
import com.sun.midp.log.LogChannels;
import com.sun.midp.main.MIDletSuiteVerifier;
import com.sun.midp.midlet.MIDletSuite;
/**
* This class extends the <code>PaymentModule</code> class with the device
* dependent methods.
*
* @version
*/
public class CldcPaymentModule extends PaymentModule {
/**
* Inner class to request security token from SecurityInitializer.
* SecurityInitializer should be able to check this inner class name.
*/
static private class SecurityTrusted
implements ImplicitlyTrustedClass {};
/** This class has a different security domain than the MIDlet suite. */
private static SecurityToken classSecurityToken =
SecurityInitializer.requestToken(new SecurityTrusted());
// === DEBUG MODE ===
/** Random generator for debug purpose */
static Random random = new Random();
// === DEBUG MODE ===
/** The name of the file where to store the Midlet PaymentID */
private static final String PAYMENT_ID_FILE_NAME = "payment_id";
/** Record ID for application payment ID store */
private static final int PAYMENT_ID_RECORD = 1;
/**
* Creates a new instance of CldcPaymentModule.
*/
protected CldcPaymentModule() {
}
/**
* It's a factory method for <code>TransactionModuleImpl</code>.
*
* @param object the application MIDlet initiating a payment transaction
* @return a new instance of a <code>TransactionModuleImpl</code> subclass.
* @throws TransactionModuleException indicates a creation failure
*/
public TransactionModuleImpl createTransactionModule(Object object) throws
TransactionModuleException {
try {
return new CldcTransactionModuleImpl(object);
} catch (TransactionModuleException ex) {
SystemAlert alert = new SystemAlert(
classSecurityToken,
utilities.getString(Utils.PAYMENT_ERROR_DLG_CAPTION),
ex.getMessage(), null, AlertType.ERROR);
alert.run();
alert.waitForUser();
throw ex;
}
}
/**
* Initializes the transaction store for the given MIDlet suite.
* <p>
* Generates fake missed transactions for the
* <code>Pay-Debug-MissedTransactions</code> debug mode.
*
* @param paymentInfo provision information
* @param suiteId MDIletSuite ID
* @param appName application name
*/
public final void initializeTransactionStore(PaymentInfo paymentInfo,
int suiteId,
String appName) {
// === DEBUG MODE ===
int paymentID = getPaymentID(suiteId);
int numMissedTransactions = paymentInfo.getDbgMissedTransactions();
if (paymentInfo.isDemoMode() && (numMissedTransactions > 0)) {
CldcTransactionStoreImpl transactionStore =
(CldcTransactionStoreImpl) getTransactionStore();
try {
transactionStore.generateFakeRecords(
paymentID,
appName,
paymentInfo,
"Feature ",
getValidProviders(paymentInfo),
numMissedTransactions);
} catch (IOException e) {
// ignore
}
}
// === DEBUG MODE ===
}
/**
* Returns the size the given MIDlet suite uses in the transaction store.
* This size doesn't include the size of the passed transactions (it
* includes only the part of the store which is removed when the MIDletSuite
* is uninstalled).
*
* @param applicationID the payment application ID of the MIDlet suite
* @return the size the MIDlet suite takes in the store
*/
public final int getSizeUsedInStore(int applicationID) {
TransactionStore transactionStore = getTransactionStore();
int size = 0;
try {
size = transactionStore.getSizeUsedByApplication(applicationID);
} catch (IOException e) {
// ignore
}
return size;
}
/**
* Uninstalls the given MIDlet suite from the transaction store. It means
* that the missed transaction records that belong to the suite are removed
* from the transaction store.
*
* @param securityToken a security token with <code>Permissions.AMS</code>
* @param applicationID the payment application ID of the MIDlet suite
*/
public final void uninstallFromStore(SecurityToken securityToken,
int applicationID) {
securityToken.checkIfPermissionAllowed(Permissions.AMS_PERMISSION_NAME);
TransactionStore transactionStore = getTransactionStore();
try {
transactionStore.removeApplicationRecords(applicationID);
} catch (IOException e) {
// ignore
}
}
/**
* Return missed(pending) transactions headers for given MIdlet suite.
*
* @param suiteId MIDlet suite ID
* @return header of missed records
*/
public final String[] getMissedRecordsHeaders(int suiteId) {
CldcTransactionStoreImpl transactionStore =
(CldcTransactionStoreImpl) getTransactionStore();
CldcTransactionRecordImpl[] recs = null;
String[] headers = null;
try {
int appID = getPaymentID(suiteId);
recs = (CldcTransactionRecordImpl[])
transactionStore.getMissedTransactions(appID);
// there is no missed transaction
if (recs == null) {
return null;
}
headers = new String[recs.length];
StringBuffer buff = new StringBuffer();
for (int i = 0; i < headers.length; i++) {
buff.setLength(0);
buff.append(recs[i].getFeatureTitle());
buff.append(": ");
buff.append(recs[i].getPrice());
buff.append(recs[i].getCurrency());
headers[i] = buff.toString();
}
} catch (IOException e) {
// skip, return as it is
}
return headers;
}
/**
* Return application payment ID for given midlet suite.
* Create such ID if it is necessary.
*
* @param suiteId suite ID
* @return payment ID
*/
public final int getPaymentID(int suiteId) {
RecordStoreImpl store = null;
int paymentID = -1;
try {
store = RecordStoreImpl.openRecordStore(
classSecurityToken, suiteId, PAYMENT_ID_FILE_NAME, false);
try {
byte[] data = new byte[4];
data = store.getRecord(1);
if (data.length == 4) {
paymentID = CldcTransactionStoreImpl.
getIntFromByteArray(data);
} else {
paymentID = -1;
}
} finally {
store.closeRecordStore();
}
} catch (RecordStoreNotFoundException ex) {
try {
int appPaymentId = PaymentModule.getInstance().
getNextApplicationID();
store = RecordStoreImpl.openRecordStore(
classSecurityToken, suiteId, PAYMENT_ID_FILE_NAME, true);
try {
byte[] data = CldcTransactionStoreImpl.
getByteArrayFromInt(appPaymentId);
store.addRecord(data, 0, data.length);
paymentID = appPaymentId;
} finally {
store.closeRecordStore();
}
} catch (RecordStoreException e) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_AMS,
"Storage Failure: Can not store Payment ID");
}
} catch (IOException e) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_AMS,
"getPaymentID threw an IOException: " +
e.getMessage());
}
}
} catch (RecordStoreException ex) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_AMS,
"Storage Failure: Can not read Payment ID");
}
}
return paymentID;
}
/**
* Remove missed transaction for given suite.
*
* @param suiteId suite ID
*/
public final void removeMissed(int suiteId) {
int id = getPaymentID(suiteId);
CldcTransactionStoreImpl transactionStore =
(CldcTransactionStoreImpl) getTransactionStore();
try {
transactionStore.removeMissedTransaction(id);
} catch (IOException e) {
// skip
}
}
/**
* The cleanUp method for the TCK tests. Should be removed when not
* needed!!! Erases the transaction store.
*
* @throws IOException indicating Transaction Store failure
*/
public final void cleanUp() throws IOException {
getTransactionStore().cleanUp();
}
// === DEBUG MODE ===
/**
* Handles the success/failure/random debug mode for the given transaction.
* It's called from the parts of the <code>PaymentModule</code> code where
* this mode can be applied. If the debug mode is in effect the transaction
* state is set accordingly and the method returns <code>true</code>.
*
* @param transaction the transaction
* @return <code>true</code> if the transaction is handled in the method
*/
protected final boolean handleTransactionDebugMode(
Transaction transaction) {
PaymentInfo paymentInfo = getPaymentInfo(transaction);
// (random.nextInt(128) < 64) = approx. one in two will fail
if (paymentInfo.isDemoMode()) {
if (paymentInfo.getDbgFailIO() ||
(paymentInfo.getDbgRandomTests() &&
(random.nextInt(128) < 64))) {
transaction.setState(Transaction.FAILED);
} else {
transaction.setState(Transaction.SUCCESSFUL);
}
transaction.setNeedsUI(false);
return true;
}
return false;
}
/**
* Handles the auto request debug mode for the given transaction. It's
* called from the parts of the <code>PaymentModule</code> code where this
* mode can be applied. If the auto request mode is in effect the
* transaction state is set accordingly and the method returns
* <code>true</code>.
*
* @param transaction the transaction
* @return <code>true</code> if the transaction is handled in the method
* (= the auto request mode is in effect)
*/
protected final boolean handleAutoRequestMode(Transaction transaction) {
PaymentInfo paymentInfo = getPaymentInfo(transaction);
if (!paymentInfo.isDemoMode()) {
return false;
}
switch (paymentInfo.getDbgAutoRequestMode()) {
case PaymentInfo.AUTO_REQUEST_REJECT:
transaction.setState(Transaction.REJECTED);
transaction.setNeedsUI(false);
break;
case PaymentInfo.AUTO_REQUEST_ACCEPT:
int[] providers = getValidProviders(paymentInfo);
// we do have at least one supported payment provider
assignTransaction(transaction, providers[0]);
break;
default:
return false;
}
return true;
}
// === DEBUG MODE ===
/** Instance of TransactionStore */
private TransactionStore transactionStore;
/**
* Init and return instance of <code>TransactionStore</code>.
*
* @return TransactionStore
*/
public TransactionStore getTransactionStore() {
if (transactionStore == null) {
try {
transactionStore =
new CldcTransactionStoreImpl(classSecurityToken);
} catch (IOException e) {
}
}
return transactionStore;
}
/** Instance of Utils class */
private Utils utilities = getUtilities();
/**
* Returns an instance of <code>CldcUtils</code> class.
*
* @return the instance
*/
protected Utils getUtilities() {
if (utilities == null) {
utilities = new CldcUtils();
}
return utilities;
}
/** an preempt token object to pass to donePreempting */
private Object PreemptToken;
/**
* Replaces the current <code>Displayable</code> with the new one if the
* <code>nextDisplayable</code> is not <code>null</code> or it recovers
* the previous <code>Displayable</code> if the <code>nextDisplayable</code>
* is <code>null</code>.
*
* @param token a security token, which allows preempting
* @param nextDisplayable the <code>Displayable</code> to show or
* <code>null</code> if the recovery of the old
* <code>Displayable</code> is requested
*/
protected void preemptDisplay(SecurityToken token,
Displayable nextDisplayable) {
DisplayEventHandler d =
DisplayEventHandlerFactory.getDisplayEventHandler(token);
if (nextDisplayable != null) {
try {
PreemptToken = d.preemptDisplay(nextDisplayable, true);
} catch (InterruptedException ex) {
if (Logging.REPORT_LEVEL <= Logging.ERROR) {
Logging.report(Logging.ERROR, LogChannels.LC_NONE,
"preemptDisplay threw an InterruptedException: " +
ex.getMessage());
}
}
} else {
d.donePreempting(PreemptToken);
PreemptToken = null;
}
}
static {
/* Hand out security token */
PPSMSAdapter.initSecurityToken(classSecurityToken);
}
}