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

PaymentInfo

public final class PaymentInfo extends Object
This class represents the payment information read from the application Manifest file or obtained from the associated update URL.
version
1.11

Fields Summary
public static final int
AUTO_REQUEST_OFF
A value indicating that the auto request mode is disabled.
public static final int
AUTO_REQUEST_ACCEPT
A value indicating that the auto request mode is set to accept.
public static final int
AUTO_REQUEST_REJECT
A value indicating that the auto request mode is set to reject.
private static final String
CURRENT_VERSION
The version number of the JAR-Manifest fields.
private static final String
PAY_VERSION
Pay version attribute name.
private static final String
PAY_ADAPTERS
Pay-adapters attribute name.
private static final String
PAY_DBG_DEMOMODE
Pay-Debug-DemoMode attribute name.
private static final String
PAY_DBG_FAILINITIALIZE
Pay-Debug-FailInitialize attribute name.
private static final String
PAY_DBG_FAILIO
Pay-Debug-FailIO attribute name.
private static final String
PAY_DBG_MISSEDTRANSACTIONS
Pay-Debug-MissedTransactions attribute name.
private static final String
PAY_DBG_RANDOMTESTS
Pay-Debug-RandomTests attribute name.
private static final String
PAY_DBG_AUTOREQUESTMODE
Pay-Debug-AutoRequestMode attribute name.
private static final String
PAY_DBG_NOADAPTER
Pay-Debug-NoAdapter attribute name.
private static final String
PAY_UPDATE_DATE
Pay-Update-Date attribute name.
private static final String
PAY_UPDATE_STAMP
Pay-Update-Stamp attribute name.
private static final String
PAY_UPDATE_URL
Pay-Update-URL attribute name.
private static final String
PAY_CACHE
Pay-Cache attribute name.
private static final String
PAY_PROVIDERS
Pay-Providers attribute name.
private static final String
PAY_PREFIX
Prefix for constructing provider specific attribute name.
private static final String
PAY_FEATURE_PREFIX
Prefix for constructing feature description attribute name.
private static final String
INFO_SUFFIX
Suffix for constructing provider info attribute name.
private static final String
TAG
Suffix for constructing price and payment specific information attribute name.
private static final String
PAY_CERTIFICATE_PREFIX
Pay-Certificate-(n)-(m) attribute name prefix.
private static final String
PAY_SIGNATURE_PREFIX
Pay-Signature-XXX-XXX attribute name prefix.
private static final String
PAY_SIGNATURE_RSA_SHA1
Pay-Signature-RSA-SHA1 attribute name.
private static final char[]
PKI_PREFIXES
PKI prefixes are used for property strip.
private static final String[]
VALID_ADAPTER_NAMES
List of supported adapters.
private static final String
YES_VALUE
Pointer to "yes" string.
private static final String
NO_VALUE
Pointer to "no" string.
private static final String[]
YES_NO_OPTIONS
Array of options could only exist in payment attributes.
private static final String[]
ACCEPT_REJECT_OPTIONS
Array of options can only exist in payment attributes.
private static final Utils
utilities
Instance of Utils class
private String[]
adapters
List of MIDlet requested adapters.
private boolean
dbgDemoMode
Pay-Debug-DemoMode attribute value.
private boolean
dbgFailInitialize
Pay-Debug-FailInitialize attribute value.
private boolean
dbgFailIO
Pay-Debug-FailIO attribute value.
private int
dbgMissedTransactions
Pay-Debug-MissedTransactions attribute value.
private boolean
dbgRandomTests
Pay-Debug-RandomTests attribute value.
private int
dbgAutoRequestMode
Pay-Debug-AutoRequestMode attribute value.
private Date
updateDate
Pay-Update-Date attribute value.
private Date
updateStamp
Pay-Update-Stamp attibute value.
private String
updateURL
Pay-Update-URL attribute value.
private boolean
cache
Pay-Cache attribute value.
private Date
expirationDate
Payment info expiration date.
private int[]
featureToTag
Array of features price tags.
private ProviderInfo[]
providers
List of MIDlet supported payment providers.
Constructors Summary
private PaymentInfo()
Default constructor.

   

       
      
    
Methods Summary
public booleancache()
Returns true if the payment information should be stored for the next time.

return
true if the payment information should be cached

        return cache;
    
private voidcheckPayVersion(java.lang.String payVersion)
Parse and check the version number of the JAR-Manifest or JAD fields.

param
payVersion version string to check
throws
PaymentException if the parameter contains wrong value or its value is greater than the version of the Payment API implemented in the device.

        double curVer;
        double appVer;
        payVersion = payVersion.trim();

        // The format must be <major>.<minor>.
        // First accepted version is 1.0
        if ('1" > payVersion.charAt(0)    || 
            -1 == payVersion.indexOf('.") ||
            2 > payVersion.length() - payVersion.indexOf('.")  ) {
            // unsupported payment version
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE, 
                    PAY_VERSION, null);
        }

        try {
            curVer = Float.parseFloat(CURRENT_VERSION);
            appVer = Float.parseFloat(payVersion);
        } catch ( NumberFormatException nfe ) {
            // unsupported payment version
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE, 
                    PAY_VERSION, null);
        }
    
        if (curVer < appVer) {
            // unsupported payment version
            throw new PaymentException(
                    PaymentException.UNSUPPORTED_PAYMENT_INFO, 
                    PAY_VERSION, null);
        }
    
public static com.sun.j2me.payment.PaymentInfocreateFromProperties(com.sun.midp.util.Properties jadProperties, com.sun.midp.util.Properties jarProperties)
Creates an instance of the PaymentInfo class. It reads information from the provided JAD and Manifest properties.

param
jadProperties the JAD properties
param
jarProperties the Manifest properties
return
the instance of the PaymentInfo class
throws
PaymentException if some of the properties are incorrect, incomplete, unsupported, etc.

        PaymentInfo paymentInfo = new PaymentInfo();
        
        paymentInfo.loadFromJadProperties(jadProperties);
        paymentInfo.loadFromJarProperties(jarProperties);
        
        return paymentInfo;        
    
public voidexport(java.io.Writer os)
Exports the payment information into the given character output stream.

param
os the output stream
throws
IOException indicates an output error

        StringBuffer buffer = new StringBuffer();
        
        // Pay-Version: 1.0
        buffer.append(PAY_VERSION);
        buffer.append(": ");
        buffer.append(CURRENT_VERSION);
        buffer.append("\n");
        
        // Pay-Update-Date: <Date>
        if (updateDate != null) {
            buffer.append(PAY_UPDATE_DATE);
            buffer.append(": ");
            buffer.append(utilities.formatISODate(updateDate.getTime()));
            buffer.append("\n");
        }
        
        // Pay-Update-Stamp: <Date>
        buffer.append(PAY_UPDATE_STAMP);
        buffer.append(": ");
        buffer.append(utilities.formatISODate(updateStamp.getTime()));
        buffer.append("\n");
        
        // Pay-Update-URL: <UpdateURL>
        buffer.append(PAY_UPDATE_URL);
        buffer.append(": ");
        buffer.append(updateURL);
        buffer.append("\n");
        
        // Pay-Cache: [yes|no|<Expiration-Date>]
        buffer.append(PAY_CACHE);
        buffer.append(": ");
        if (expirationDate != null) {
            buffer.append(utilities.formatISODate(expirationDate.getTime()));
        } else {
            buffer.append(cache ? YES_VALUE : NO_VALUE);
        }
        buffer.append("\n");
        
        // Pay-Feature-<n>: <m>
        for (int i = 0; i < featureToTag.length; ++i) {
            buffer.append(PAY_FEATURE_PREFIX);
            buffer.append(i);
            buffer.append(": ");
            buffer.append(featureToTag[i]);
            buffer.append("\n");
        }
        
        os.write(buffer.toString());
        buffer.setLength(0);

        // Pay-Providers: <ProviderTitles>
        buffer.append(PAY_PROVIDERS);
        buffer.append(": ");
        buffer.append(providers[0].getName());
        for (int i = 1; i < providers.length; ++i) {
            buffer.append(", ");
            buffer.append(providers[i].getName());
        }
        buffer.append("\n");
        
        for (int i = 0; i < providers.length; ++i) {
            exportProvider(buffer, providers[i]);
        }
        
        os.write(buffer.toString());
    
private voidexportProvider(java.lang.StringBuffer buffer, ProviderInfo provider)
Exports the given provider information into the given StringBuffer.

param
buffer the StringBuffer
param
provider the provider information

        
        // Pay-<ProviderTitle>
        String providerPrefix = PAY_PREFIX + provider.getName();
        
        // Pay-<ProviderTitle>-Info: <RegAdapter>, <ISO4217CurrencyCode>,
        //      <PaymentSpecificInformation>
        buffer.append(providerPrefix);
        buffer.append(INFO_SUFFIX);
        buffer.append(": ");
        buffer.append(provider.getAdapter());
        buffer.append(", ");
        buffer.append(provider.getCurrency());
        buffer.append(", ");
        buffer.append(provider.getConfiguration());
        buffer.append("\n");

        // Pay-<ProviderTitle>-Tag-<m>: <Price>[, 
        //      <PaymentSpecificPriceInformation>]
        int count = provider.getNumPriceTags();
        for (int i = 0; i < count; ++i) {
            buffer.append(providerPrefix);
            buffer.append(TAG);
            buffer.append(i);
            buffer.append(": ");
            buffer.append(provider.getPrice(i));
            String value = provider.getPaySpecificPriceInfo(i);
            if (value != null) {
                buffer.append(", ");
                buffer.append(value);
            }
            buffer.append("\n");
        }
    
private static X509CertificatefindTrustedCertificate(com.sun.midp.util.Properties props)
Finds a trusted provider certificate in one of the certification chains read from the given properties.

param
props the properties
return
the trusted provider certificate
throws
PaymentException if some certification chain is incorrect or none of them can be trusted

        int certPath = 1;
        int certIndex = 1;
        Vector certificates = new Vector();
        String encodedCert = props.getProperty(PAY_CERTIFICATE_PREFIX + 
                certPath + "-" + certIndex);

        if (encodedCert == null) {
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE,
                    PAY_CERTIFICATE_PREFIX + certPath + "-" + certIndex,
                    null);
        }
        
        do {
            certificates.setSize(0);
            
            do {
                try {
                    byte[] binaryCert = Base64.decode(encodedCert);
                    certificates.addElement(X509Certificate.generateCertificate(
                            binaryCert, 0, binaryCert.length));
                } catch (IOException e) {
                    throw new PaymentException(
                        PaymentException.INVALID_ATTRIBUTE_VALUE, 
                        PAY_CERTIFICATE_PREFIX + certPath + "-" + certIndex, 
                        "invalid or unsupported certificate");
                }
                
                encodedCert = props.getProperty(PAY_CERTIFICATE_PREFIX +
                        certPath + "-" + ++certIndex);
            } while (encodedCert != null);
            
            try {
                String[] authPath = X509Certificate.verifyChain(certificates, 
                        X509Certificate.DIGITAL_SIG_KEY_USAGE,
                        X509Certificate.CODE_SIGN_EXT_KEY_USAGE,
                        WebPublicKeyStore.getTrustedKeyStore());
                String domain = Permissions.UNIDENTIFIED_DOMAIN_BINDING;
                Vector keys = WebPublicKeyStore.getTrustedKeyStore().
                     findKeys(authPath[0]);
                if (keys != null) {
                    domain = ((PublicKeyInfo)keys.elementAt(0)).getDomain();
                }

                if (!Permissions.UNIDENTIFIED_DOMAIN_BINDING.equals(domain)) {
                    // we verified the chain
                    return (X509Certificate)certificates.elementAt(0);
                }

                // try next chain
                
            } catch (CertificateException e) {
                switch (e.getReason()) {
                    case CertificateException.UNRECOGNIZED_ISSUER:
                        // try next chain
                        break;
                    case CertificateException.EXPIRED:
                    case CertificateException.NOT_YET_VALID:
                        throw new PaymentException(
                                PaymentException.EXPIRED_PROVIDER_CERT, 
                                e.getCertificate().getSubject(), null);
                    case CertificateException.ROOT_CA_EXPIRED:
                        throw new PaymentException(
                                PaymentException.EXPIRED_CA_CERT, 
                                e.getCertificate().getIssuer(), null);
                    default:
                        throw new PaymentException(
                                PaymentException.INVALID_PROVIDER_CERT, 
                                e.getCertificate().getSubject(), null);
                }
            }
            
            certIndex = 1;
            encodedCert = props.getProperty(PAY_CERTIFICATE_PREFIX +
                    ++certPath + "-" + certIndex);
        } while (encodedCert != null);
        
        throw new PaymentException(PaymentException.NO_TRUSTED_CHAIN);
    
public intgetDbgAutoRequestMode()
Returns the debug auto request mode setting.

return
AUTO_REQUEST_OFF if the auto request mode is disabled, AUTO_REQUEST_ACCEPT if the auto request mode is set to accept and AUTO_REQUEST_REJECT if it is set to reject
see
#AUTO_REQUEST_OFF
see
#AUTO_REQUEST_ACCEPT
see
#AUTO_REQUEST_REJECT

        return dbgAutoRequestMode;
    
public booleangetDbgFailIO()
Test for the debug fail IO mode.

return
true if the debug fail IO mode should be activated

        return dbgFailIO;
    
public booleangetDbgFailInitialize()
Test for the debug fail initialize mode.

return
true if the debug fail initialize mode should be activated

        return dbgFailInitialize;
    
public intgetDbgMissedTransactions()
Returns the number of fake missed transactions that should be generated when the application starts.

return
the number of missed transactions to generate or -1 if this debug mode is disabled

        return dbgMissedTransactions;
    
public booleangetDbgRandomTests()
Test for the debug random tests mode.

return
true if the debug random tests mode should be activated

        return dbgRandomTests;
    
public intgetNumFeatures()
Gets the number of features the application can request the user to pay for.

return
the number of paid features

        return featureToTag.length;
    
public intgetNumProviders()
Returns the number of providers which can be used to pay for the application features.

return
the number of providers

        return providers.length;
    
public intgetPriceTagForFeature(int index)
Returns the price tag for the given feature id.

param
index the feature id
return
the price tag

        return featureToTag[index];
    
public ProviderInfogetProvider(int index)
Return the provider information for the given provider id.

param
index the provider id
return
the provider information

        return providers[index];
    
public java.util.DategetUpdateDate()
Returns the date of the last update or null if the payment information has been never updated.

return
the last update date or null

        return updateDate;
    
public java.util.DategetUpdateStamp()
Gets the time stamp of last update.

return
the time stamp

        return updateStamp;
    
public java.lang.StringgetUpdateURL()
Returns the URL of the payment update.

return
the update URL

        return updateURL;
    
private booleanhasDuplicates(java.util.Vector vector)
Returns true if the given vector contains duplicate values.

param
vector the vector of strings
return
true if the vector contains duplicate values

        int lastIndex = vector.size() - 1;
        for (int i = 0; i < lastIndex; ++i) {
            if (vector.indexOf(vector.elementAt(i), i + 1) != -1) {
                return true;
            }
        }
        
        return false;
    
private native booleanisDebugMode()
Test for the system debug mode.

return
true if the is running in the system debug mode

public booleanisDemoMode()
Test for the debug demo mode.

return
true if the debug demo mode should be activated

        return dbgDemoMode && isDebugMode();
    
private voidloadFromJadProperties(com.sun.midp.util.Properties props)
Updates the payment information from the given JAD file properties. If an exception is thrown during the update the original object state remains intact.

param
props the JAD file properties
throws
PaymentException if the data read are incorrect or incomplete

        String payVersion = props.getProperty(PAY_VERSION);
        String payAdapters = props.getProperty(PAY_ADAPTERS);
     
        if (payVersion != null) {
            checkPayVersion(payVersion);
        
            if (payAdapters == null) {
                // missing PAY_ADAPTERS attribute
                throw new PaymentException(
                        PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                        PAY_ADAPTERS, null);
            }
        } else {
            if (payAdapters != null) {
                // missing PAY_VERSION attribute
                throw new PaymentException(
                        PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                        PAY_VERSION, null);
            }
        }
        
        String[] adapters = null;
        // read & validate adapters
        if (payVersion != null) {
            Vector names = Util.getCommaSeparatedValues(payAdapters);

            if (names.size() == 0) {
                throw new PaymentException(
                        PaymentException.INVALID_ATTRIBUTE_VALUE, 
                        PAY_ADAPTERS, "the value is empty");
            }
            
            // avoid duplicate names
            if (hasDuplicates(names)) {
                throw new PaymentException(
                        PaymentException.INVALID_ATTRIBUTE_VALUE, 
                        PAY_ADAPTERS, "duplicate fields in the value");
            }
            
            adapters = toStringArray(names);
            
            // validate adapter names
            for (int i = 0; i < adapters.length; ++i) {
                if (!validateAdapterName(adapters[i])) {
                    throw new PaymentException(
                            PaymentException.INVALID_ATTRIBUTE_VALUE, 
                            PAY_ADAPTERS, adapters[i] + " is not a valid " +
                                "adapter name");
                }
            }
            
            // validate supported adapters
            PaymentModule paymentModule = PaymentModule.getInstance();
            int j;
            
            for (j = 0; j < adapters.length; ++j) {
                if (paymentModule.isSupportedAdapter(adapters[j])) {
                    break;
                }
            }

            if (j == adapters.length) {
                throw new PaymentException(
                        PaymentException.UNSUPPORTED_ADAPTERS,
                        PAY_ADAPTERS, null);
            }
        }

        // read and validate Pay-Debug-* attributes
        boolean dbgDemoMode = readOptionalSelection(props, 
                PAY_DBG_DEMOMODE, YES_NO_OPTIONS, 1) != 1;
        boolean dbgFailInitialize = readOptionalSelection(props, 
                PAY_DBG_FAILINITIALIZE, YES_NO_OPTIONS, 1) != 1;
        boolean dbgFailIO = readOptionalSelection(props, 
                PAY_DBG_FAILIO, YES_NO_OPTIONS, 1) != 1;
        int dbgMissedTransactions = -1;
        boolean dbgRandomTests = readOptionalSelection(props, 
                PAY_DBG_RANDOMTESTS, YES_NO_OPTIONS, 1) != 1;
        int dbgAutoRequestMode = readOptionalSelection(props,
                PAY_DBG_AUTOREQUESTMODE, ACCEPT_REJECT_OPTIONS, -1) + 1;

        // Peyment spec 1.1
        // It is not used yet, but need for TCK passing
        boolean dbgNoAdapter = readOptionalSelection(props,
                PAY_DBG_NOADAPTER, YES_NO_OPTIONS, 1) != 1;

        String dbgMissedTransactionsStr = props.getProperty(
                PAY_DBG_MISSEDTRANSACTIONS);
        if (dbgMissedTransactionsStr != null) {
            try {
                dbgMissedTransactions = Integer.parseInt(
                        dbgMissedTransactionsStr);
            } catch (NumberFormatException e) {
            }
            
            if (dbgMissedTransactions < 0) {
                throw new PaymentException(
                        PaymentException.INVALID_ATTRIBUTE_VALUE, 
                        PAY_DBG_MISSEDTRANSACTIONS, 
                        "expecting a positive number");
            }
        }
        
        // everything is correct, let's change the object state
        this.adapters = adapters;
        this.dbgDemoMode = dbgDemoMode;
        this.dbgFailInitialize = dbgFailInitialize;
        this.dbgFailIO = dbgFailIO;
        this.dbgMissedTransactions = dbgMissedTransactions;
        this.dbgRandomTests = dbgRandomTests;
        this.dbgAutoRequestMode = dbgAutoRequestMode;
    
private voidloadFromJarProperties(com.sun.midp.util.Properties props)
Updates the payment information from the given Manifest properties. If an exception is thrown during the update the original object state remains intact.

param
props the Manifest properties
throws
PaymentException if the data read are incorrect or incomplete

        loadFromPropertiesAux(props, false);
        
        // load the update date
        String tempValue = props.getProperty(PAY_UPDATE_DATE);
        updateDate = null;
        if (tempValue != null) {
            try {
                long millis = utilities.parseISODate(tempValue);
                updateDate = new Date(millis);
            } catch (IllegalArgumentException e) {
            }
        }
    
private voidloadFromJppProperties(com.sun.midp.util.Properties props)
Updates the payment information from the given update file properties. If an exception is thrown during the update the original object state remains intact.

param
props the update file properties
throws
PaymentException if the data read are incorrect or incomplete

        loadFromPropertiesAux(props, true);
    
private voidloadFromPropertiesAux(com.sun.midp.util.Properties props, boolean strict)
Loads the payment information from the given Manifest properties or update file properties. The strict indicates if the additional tests should be executed on the data read from the properties. After passing these additional tests the resulting payment information can be used for payment without any further update. Should an exception be thrown during the loading the object state will remain intact.

param
props the properties
param
strict if true the requirements on the data read from the properties are harder
throws
PaymentException if the data read are incorrect or incomplete

        String tempValue;
        
        long currentTime = System.currentTimeMillis();
        
        tempValue = props.getProperty(PAY_VERSION);
        
        if (tempValue == null) {
            // missing PAY_VERSION attribute
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                    PAY_VERSION, null);
        }

        // throws Payment exception 
        // if app pay version is greater than stack version
        checkPayVersion(tempValue);
        
        tempValue = props.getProperty(PAY_UPDATE_STAMP);
        Date updateStamp;
        
        if (tempValue == null) {
            // missing PAY_UPDATE_STAMP attribute
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                    PAY_UPDATE_STAMP, null);
        }
        
        // parse and validate the date
        try {
            long millis = utilities.parseISODate(tempValue);
            if (millis > currentTime) {
                throw new PaymentException(
                        PaymentException.INFORMATION_NOT_YET_VALID);
            }
            updateStamp = new Date(millis);
        } catch (IllegalArgumentException e) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE,
                    PAY_UPDATE_STAMP, e.getMessage());
        }
        
        String updateURL = props.getProperty(PAY_UPDATE_URL);
        
        if (updateURL == null) {
            // missing PAY_UPDATE_URL attribute
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                    PAY_UPDATE_URL, null);
        }
        
        // validate the URL
        try {
            HttpUrl tempURL = new HttpUrl(updateURL);
            if (!"http".equals(tempURL.scheme) && 
                    !"https".equals(tempURL.scheme)) {
                throw new PaymentException(
                        PaymentException.UNSUPPORTED_URL_SCHEME,
                        tempURL.scheme, null);
            }

        } catch (IllegalArgumentException e) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE,
                    PAY_UPDATE_URL, e.getMessage());
        }
        
        tempValue = props.getProperty(PAY_CACHE);
        boolean cache = true;
        Date expirationDate = null;
        
        // validate and parse the PAY_CACHE attribute
        if (tempValue != null) {
            if (YES_VALUE.equals(tempValue)) {
                cache = true;
            } else if (NO_VALUE.equals(tempValue)) {
                cache = false;
            } else {
                try {
                    long millis = utilities.parseISODate(tempValue);
                    if (strict && (millis < currentTime)) {
                        throw new PaymentException(
                                PaymentException.INFORMATION_EXPIRED);
                    }
                    expirationDate = new Date(millis);
                } catch (IllegalArgumentException e) {
                    throw new PaymentException( 
                            PaymentException.INVALID_ATTRIBUTE_VALUE,
                            PAY_CACHE, "expecting yes, no or a valid date");
                }
            }
        }
        
        Vector tempVector = new Vector();

        tempValue = props.getProperty(PAY_FEATURE_PREFIX + 0);

        if (tempValue == null) {
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                    PAY_FEATURE_PREFIX + 0, null);
        }

        // read Pay-Feature-<n>
        int index = 0;
        tempVector.setSize(0);
        do {
            tempVector.addElement(tempValue);
            tempValue = props.getProperty(PAY_FEATURE_PREFIX + ++index);
        } while (tempValue != null);

        int maxTag = 0;
        int[] featureToTag = new int[index];
        // parse and validate the numbers
        for (int i = 0; i < index; ++i) {
            int value = -1;
            try {
                value = Integer.parseInt((String)tempVector.elementAt(i));
            } catch (NumberFormatException e) {
            }

            if (value < 0) {
                throw new PaymentException(
                        PaymentException.INVALID_ATTRIBUTE_VALUE,
                        PAY_FEATURE_PREFIX + i, "expecting a positive number");
            }
            
            if (maxTag < value) {
                maxTag = value;
            }

            featureToTag[i] = value;
        }

        tempValue = props.getProperty(PAY_PROVIDERS);
        
        if (tempValue == null) {
            // missing PAY_PROVIDERS attribute
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                    PAY_PROVIDERS, null);
        }
        
        Vector names = Util.getCommaSeparatedValues(tempValue);

        if (names.size() == 0) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE, 
                    PAY_PROVIDERS, "the value is empty");
        }

        // avoid duplicate names
        if (hasDuplicates(names)) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE, 
                    PAY_PROVIDERS, "duplicate fields in the value");
        }
        
        // IMPL_NOTE: check provider name?
        
        ProviderInfo[] providers = new ProviderInfo[names.size()];
        int numTags = maxTag + 1;
        boolean hasSupportedProvider = false;
        PaymentModule paymentModule = PaymentModule.getInstance();
        // read and validate provider infos
        for (int i = 0; i < providers.length; ++i) {
            // read provider
            ProviderInfo provider = loadProvider(
                    props, (String)names.elementAt(i));

            if ((strict || (provider.getNumPriceTags() != 0))
                    && (provider.getNumPriceTags() < numTags)) {
                throw new PaymentException(
                        PaymentException.INCOMPLETE_INFORMATION);
            }
            
            // try to create an adapter for the provider
            PaymentAdapter adapter;
            try {
                adapter = paymentModule.getAdapter(provider.getAdapter(),
                    provider.getConfiguration());
            } catch (PaymentException e) {
                e.setParam(PAY_PREFIX + provider.getName() + INFO_SUFFIX);
                throw e;
            }
            
            if (adapter != null) {
                // adapter has been created == we support at least one payment
                // provider
                hasSupportedProvider = true;
                
                int numTags2 = provider.getNumPriceTags();
                for (int j = 0; j < numTags2; ++j) {
                    try {
                        adapter.validatePriceInfo(provider.getPrice(j),
                                provider.getPaySpecificPriceInfo(j));
                    } catch (PaymentException e) {
                        e.setParam(PAY_PREFIX + provider.getName() + TAG + j);
                        throw e;
                    }
                }
            }
            
            providers[i] = provider;
        }

        if (!hasSupportedProvider) {
            throw new PaymentException(
                    PaymentException.UNSUPPORTED_PROVIDERS,
                    PAY_PROVIDERS, null);
        }
        
        // everything is correct, let's change the object state
        this.updateStamp = updateStamp;
        this.updateURL = updateURL;
        this.cache = cache;
        this.expirationDate = expirationDate;
        this.featureToTag = featureToTag;
        this.providers = providers;
    
private ProviderInfoloadProvider(com.sun.midp.util.Properties props, java.lang.String provider)
Parses and returns the provider information for the given provider name from the properties.

param
props the properties to get provider from
param
provider the provider name
return
the provider information
throws
PaymentException if the provider information is incorrect or incomplete

        String tempValue;
        String tempKey = PAY_PREFIX + provider + INFO_SUFFIX;
        
        tempValue = props.getProperty(tempKey);
        
        if (tempValue == null) {
            // missing or incorrect provider
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                    tempKey, null);
        }

        int offset = 0;
        int index;
        
        index = tempValue.indexOf(',");
        if (index == -1) {
            // missing currency code
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE,
                    tempKey, "the currency code is not present");
        }

        String adapter = tempValue.substring(offset, index).trim();
        // validate adapter name
        if (!validateAdapterName(adapter)) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE,
                    tempKey, adapter + " is not a valid adapter name");
        }
        
        offset = index + 1;
        index = tempValue.indexOf(',", offset);
        
        String currency;
        if (index == -1) {
            currency = tempValue.substring(offset).trim();
        } else {
            currency = tempValue.substring(offset, index).trim();
        }
        // validate currency
        if (!validateCurrencyCode(currency)) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE,
                    tempKey, "not a valid currency code");
        }

        // get configuration
        String configuration;
        if (index == -1) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE,
                    tempKey, "the payment specific info is not present");
        }

        offset = index + 1;
        configuration = tempValue.substring(offset).trim();
        
        tempKey = PAY_PREFIX + provider + TAG;
        tempValue = props.getProperty(tempKey + 0);
        double[] prices = null;
        String[] paySpecificPriceInfo = null;
        if (tempValue != null) {
            // contains tag attributes
            int numTags = 0;
            Vector tempVector = new Vector();
            do {
                tempVector.addElement(tempValue);
                tempValue = props.getProperty(tempKey + ++numTags);
            } while (tempValue != null);

            // we know the number of tags
            prices = new double[numTags];
            paySpecificPriceInfo = new String[numTags];

            for (int i = 0; i < numTags; ++i) {
                tempValue = (String)tempVector.elementAt(i);
                
                index = tempValue.indexOf(',");
                
                // parse and validate the price
                try {
                    String tempPrice;
                    if (index == -1) {
                        tempPrice = tempValue.trim();
                    } else {
                        tempPrice = tempValue.substring(0, index).trim();
                    }
                    prices[i] = Double.parseDouble(tempPrice);
                } catch (NumberFormatException e) {
                    throw new PaymentException(
                            PaymentException.INVALID_ATTRIBUTE_VALUE, 
                            tempKey + i, "invalid price");
                }
                
                // get pay specific price info if present
                if (index != -1) {
                    paySpecificPriceInfo[i] = 
                            tempValue.substring(index + 1).trim();
                }
            }
        }
        
        // everything is correct, create the object
        return new ProviderInfo(provider, adapter, configuration, currency, 
                prices, paySpecificPriceInfo);
    
public booleanneedsUpdate()
Test if the payment information can be used for payment as is or it needs to be updated first from the update URL.

return
true if the payment information needs to be updated

        // 1. no cache => update
        if (!cache) {
            return true;
        }
        
        // 2. expired cache => update
        if (expirationDate != null) {
            long currentTime = System.currentTimeMillis();
            if (currentTime > expirationDate.getTime()) {
                return true;
            }
        }
        
        // 3. missing tags => update
        for (int i = 0; i < providers.length; ++i) {
            if (providers[i].getNumPriceTags() == 0) {
                return true;
            }
        }
        
        return false;
    
private intreadOptionalSelection(com.sun.midp.util.Properties props, java.lang.String attribute, java.lang.String[] options, int defValue)
Parses an attribute which can have only one of the given predefined values. It returns the index of the attribute's value or the defValue if the attribute is not defined.

param
props the properties to read the attribute from
param
attribute the name of the attribute
param
options the predefined values
param
defValue a value to return when the attribute is not defined
return
the index of a string from options which equals to the attribute's value or defValue
throws
PaymentException if the attribute's value doesn't match any of the predefined values

        String value = props.getProperty(attribute);
        if (value == null) {
            return defValue;
        }
        
        for (int i = 0; i < options.length; ++i) {
            if (options[i].equals(value)) {
                return i;
            }
        }
        
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("expecting ");
        buffer.append(options[0]);
        int i;
        for (i = 1; i < (options.length - 1); ++i) {
            buffer.append(", ");
            buffer.append(options[i]);
        }
        buffer.append(" or ");
        buffer.append(options[i]);

        throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE, 
                    attribute, buffer.toString());
    
private static java.lang.StringremovePKIProperties(java.lang.String string)
Strips all empty lines and lines containing any of Pay-Certificate-* or Pay-Signature-* attributes from the given string.

param
string property containing string
return
the altered string

        char[] data = string.toCharArray();
        int length = data.length;
        StringBuffer buffer = new StringBuffer();
        
        int i = 0;
        int j, k;
        do {
            // skip empty lines
            for (j = i; (j < length) && (data[j] != '\n") && (data[j] <= ' ");
                ++j) {
            }
            if (j == length) {
                break;
            }
            if (data[j] == '\n") {
                i = j + 1;
                continue;
            }
            
            // find matching prefix
            int prefixIdx;
            for (prefixIdx = 0; prefixIdx < PKI_PREFIXES.length; ++prefixIdx) {
                char[] prefix = PKI_PREFIXES[prefixIdx];
                for (k = 0, j = i; (j < length) && (k < prefix.length) &&
                        (data[j] == prefix[k]); ++j, ++k) {
                }
                if (k == prefix.length) {
                    break;
                }
            }

            // find the end of the line
            for (j = i; (j < length) && (data[j] != '\n"); ++j) {
            }
            if (j < length) {
                // skip '\n'
                ++j;
            }
            
            // accept the lines that don't start with any of PKI_PREFIXES
            if (prefixIdx == PKI_PREFIXES.length) {
                buffer.append(data, i, j - i);
            }
            i = j;
        } while (i < length);
        
        return buffer.toString();
    
private java.lang.String[]toStringArray(java.util.Vector vector)
Constructs an string array from the given vector of strings. The resulting array will contain the same strings as the vector and in the same order as appeared in the vector.

param
vector the vector of strings
return
the array of strings

        String[] strings = new String[vector.size()];
        vector.copyInto(strings);
        
        return strings;
    
public voidupdatePaymentInfo(byte[] data, java.lang.String charset)
Validates the given payment update and if correct it updates the internal state of the object accordingly.

param
data a byte array which contains the payment update
param
charset the character set of the payment update
throws
PaymentException if the payment update is incorrect

        Properties props;

        InputStream bis = new ByteArrayInputStream(data);
        try {
            try {
                props = utilities.loadProperties(bis, charset);
            } finally {
                bis.close();
            }
        } catch (UnsupportedEncodingException e) {
            throw new PaymentException(
                    PaymentException.UNSUPPORTED_UPDATE_CHARSET, 
                    charset, null);
        } catch (IOException e) {
            throw new PaymentException(
                    PaymentException.INVALID_PROPERTIES_FORMAT,
                    e.getMessage());
        }
        
        // find a trusted provider certificate in one of the certification 
        // chains of the payment update
        X509Certificate trustedCertificate = findTrustedCertificate(
                props);

        // get the public key for the trusted certificate
        PublicKey publicKey;
        try {
            publicKey = trustedCertificate.getPublicKey();
        } catch (CertificateException e) {
            throw new PaymentException(
                    PaymentException.INVALID_PROVIDER_CERT, 
                    trustedCertificate.getSubject(), null);
        }
        
        // get the encoded signature
        String encodedSignature = props.getProperty(PAY_SIGNATURE_RSA_SHA1);
        if (encodedSignature == null) {
            throw new PaymentException(
                    PaymentException.MISSING_MANDATORY_ATTRIBUTE, 
                    PAY_SIGNATURE_RSA_SHA1, null);
        }
        
        byte[] signature;
        try {
            signature = Base64.decode(encodedSignature);
        } catch (IOException e) {
            throw new PaymentException(
                    PaymentException.INVALID_ATTRIBUTE_VALUE, 
                    PAY_SIGNATURE_RSA_SHA1, "invalid or unsupported signature");
        }
        
        // get the data for verification
        String propString;
        byte[] testData;
        
        try {
             propString = new String(data, charset);
             testData = removePKIProperties(propString).getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new PaymentException(
                    PaymentException.UNSUPPORTED_UPDATE_CHARSET, 
                    charset, null);
        }
        
        // verify the signature
        try {
            Signature sigVerifier = Signature.getInstance("SHA1withRSA");

            sigVerifier.initVerify(publicKey);
            
            sigVerifier.update(testData, 0, testData.length);
            if (!sigVerifier.verify(signature)) {
                throw new PaymentException(
                    PaymentException.SIGNATURE_VERIFICATION_FAILED);
            }
        } catch (GeneralSecurityException e) {
            throw new PaymentException(
                    PaymentException.SIGNATURE_VERIFICATION_FAILED);
        }
        
        // validate and accept new values
        loadFromJppProperties(props);
        updateDate = new Date();
    
private booleanvalidateAdapterName(java.lang.String name)
Returns true if the given name is a valid adapter name.

param
name the name to test
return
true if the name is a valid adapter name

        if (name.startsWith("X-")) {
            return name.length() > 2;
        }
        
        for (int i = 0; i < VALID_ADAPTER_NAMES.length; ++i) {
            if (VALID_ADAPTER_NAMES[i].equals(name)) {
                return true;
            }
        }
        
        return false;
    
private booleanvalidateCurrencyCode(java.lang.String name)
Returns true if the given string value represents a valid currency code.

param
name the string to test
return
true if the string is a valid currency code

        if (name.length() != 3) {
            return false;
        }
        
        for (int i = 0; i < 3; ++i) {
            if ((name.charAt(i) < 'A") ||
                    (name.charAt(i) > 'Z")) {
                return false;
            }
        }
        
        return true;
    
public static voidvalidateJadProperties(com.sun.midp.util.Properties jadProperties)
Validates JAD properties.

param
jadProperties the JAD properties
throws
PaymentException if some of the properties are incorrect, incomplete, unsupported, etc.

        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.loadFromJadProperties(jadProperties);