001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.j2me.payment;
028:
029: import com.sun.midp.security.*;
030: import java.util.Vector;
031: import javax.microedition.io.Connector;
032: import javax.microedition.lcdui.Command;
033: import javax.microedition.lcdui.CommandListener;
034: import javax.microedition.lcdui.Displayable;
035: import javax.microedition.lcdui.Form;
036: import javax.wireless.messaging.BinaryMessage;
037: import javax.wireless.messaging.Message;
038: import javax.wireless.messaging.MessageConnection;
039: import javax.wireless.messaging.TextMessage;
040:
041: /**
042: *
043: * @created June 9, 2005
044: * @version 1.4
045: */
046:
047: /**
048: * This Premium Priced SMS (PPSMS) Adapter sends messages to a PPSMS number,
049: * which defines a payment model. The value of a transaction is determined
050: * by the premium priced number to which the SMS is sent and/or by the content
051: * described in the body of that SMS.
052: */
053: public class PPSMSAdapter extends PaymentAdapter {
054: private static final int SMS_LENGTH = 140;
055:
056: /**
057: * This class has a different security domain than the MIDlet suite
058: */
059: private static SecurityToken classSecurityToken;
060:
061: /**
062: * Initializes the security token for this class, so it can
063: * perform actions that a normal MIDlet Suite cannot.
064: *
065: * @param token security token for this class.
066: */
067: public static void initSecurityToken(SecurityToken token) {
068: if (classSecurityToken != null) {
069: return;
070: }
071:
072: classSecurityToken = token;
073: }
074:
075: /**
076: * Separate thread conducting the transaction
077: */
078: private class PPSMSThread extends Thread {
079: private Transaction transaction;
080:
081: /**
082: * Creates a new instance of PPSMSThread
083: */
084: public PPSMSThread(Transaction transaction) {
085: this .transaction = transaction;
086: }
087:
088: /**
089: * Sends Premium Priced SMS to a number from adapter configuration
090: */
091: public void run() {
092:
093: byte[] payload = transaction.getPayload();
094:
095: Object[] specInfo = parseAndValidateSpecInfo(transaction
096: .getSpecificPriceInfo());
097:
098: String msisdn = (String) specInfo[0];
099: String prefix = (String) specInfo[1];
100: int smsCount = 1;
101:
102: if (specInfo.length > 2) {
103: smsCount = ((Integer) specInfo[2]).intValue();
104: }
105:
106: try {
107: Message msg = null;
108: MessageConnection conn = (MessageConnection) Connector
109: .open("sms://" + msisdn);
110:
111: byte[] prefixBytes = prefix.getBytes("UTF-8");
112: int messageLength = prefixBytes.length
113: + (payload == null ? 0 : payload.length);
114: if (messageLength > SMS_LENGTH) {
115: // IMPL_NOTE correcly process exception
116: throw new Exception(
117: "Message to be sent is too long");
118: }
119: byte[] message = new byte[messageLength];
120:
121: System.arraycopy(prefixBytes, 0, message, 0,
122: prefixBytes.length);
123:
124: if (payload != null) {
125: System.arraycopy(payload, 0, message,
126: prefixBytes.length, payload.length);
127: }
128:
129: msg = (BinaryMessage) conn
130: .newMessage(MessageConnection.BINARY_MESSAGE);
131:
132: ((BinaryMessage) msg).setPayloadData(message);
133:
134: for (int i = 0; i < smsCount; i++) {
135: conn.send(msg);
136: }
137:
138: conn.close();
139: transaction.setState(Transaction.SUCCESSFUL);
140: } catch (Exception e) {
141: // IMPL_NOTE correcly process exception
142: // ErrorForm errorForm = new ErrorForm(transaction);
143: // preemptDisplay(classSecurityToken, errorForm);
144:
145: // the transaction failed
146: transaction.setState(Transaction.FAILED);
147: }
148:
149: transaction.setWaiting(false);
150: }
151: }
152:
153: /**
154: * Displays error message in case that SMS cannot be sent
155: */
156: private class ErrorForm extends Form implements CommandListener {
157: private Transaction transaction;
158:
159: private final Command okCommand = new Command("Ok", Command.OK,
160: 1);
161:
162: /**
163: * Creates a new instance of ErrorForm
164: */
165: public ErrorForm(Transaction transaction) {
166: super ("Communication Error!");
167: append("The Premium Priced SMS cannot be sent");
168: this .transaction = transaction;
169: addCommand(okCommand);
170: setCommandListener(this );
171: }
172:
173: public void commandAction(Command c, Displayable d) {
174: preemptDisplay(classSecurityToken, null);
175:
176: // the transaction failed
177: transaction.setState(Transaction.FAILED);
178: transaction.setNeedsUI(false);
179: }
180: }
181:
182: /**
183: * Creates a new instance of PPSMSAdapter
184: */
185: private PPSMSAdapter() {
186: }
187:
188: /**
189: * Gets a display name of this adapter.
190: *
191: * @return the display name
192: */
193: public String getDisplayName() {
194: return "Premium Priced SMS";
195: }
196:
197: /**
198: * Gets a new instance of this adapter.
199: *
200: * @param configuration configuration info of PPSMSAdapter
201: * @return the new instance of PPSMSAdapter
202: */
203: public static PPSMSAdapter getInstance(String configuration) {
204: return new PPSMSAdapter();
205: }
206:
207: /**
208: * Validates the price information which are specified in the application
209: * manifest file for the provider handled by this adapter. It throws an
210: * <code>PaymentException</code> if the parameters are incorrect.
211: *
212: * @param price the price to pay when using this provider
213: * @param paySpecificPriceInfo the specific price information string from
214: * the manifest
215: * @throws PaymentException if the provided information is correct
216: */
217: public void validatePriceInfo(double price,
218: String paySpecificPriceInfo) throws PaymentException {
219:
220: if ((paySpecificPriceInfo == null)
221: || (paySpecificPriceInfo.length() == 0)
222: || parseAndValidateSpecInfo(paySpecificPriceInfo) == null) {
223: throw new PaymentException(
224: PaymentException.INVALID_PRICE_INFORMATION,
225: paySpecificPriceInfo);
226: }
227:
228: }
229:
230: /**
231: * Parses and validates specific payment information from input parameter
232: *
233: * @param paySpecificInfo the string representing comma-separated
234: * specific payment information
235: * @return array of specific information if all information is valid,
236: * otherwise <code>null</code>
237: */
238: private Object[] parseAndValidateSpecInfo(String paySpecificInfo) {
239: Object[] info = null;
240: int firstIndex = paySpecificInfo.indexOf(',');
241:
242: if (firstIndex == -1) {
243: return null;
244: }
245:
246: int lastIndex = paySpecificInfo.lastIndexOf(',');
247: String msisdn = paySpecificInfo.substring(0, firstIndex).trim();
248:
249: // check if msisdn is in right format
250: try {
251: long i = msisdn.startsWith("+") ? Long.parseLong(msisdn
252: .substring(1)) : Long.parseLong(msisdn);
253: } catch (NumberFormatException nfe) {
254: return null;
255: }
256:
257: String prefix = "";
258: String smsCount = null;
259:
260: // no number of messages parameter
261: if (firstIndex == lastIndex) {
262: prefix = paySpecificInfo.substring(firstIndex + 1,
263: paySpecificInfo.length()).trim();
264: } else {
265: prefix = paySpecificInfo.substring(firstIndex + 1,
266: lastIndex).trim();
267:
268: smsCount = paySpecificInfo.substring(lastIndex + 1,
269: paySpecificInfo.length()).trim();
270: }
271:
272: // check if prefix meets all conditions described in specification
273: if (!prefix.equals("")) {
274: int maxLength = 8;
275: if (prefix.startsWith("0x") || prefix.startsWith("0X")) {
276: if (prefix.length() % 2 != 0) {
277: // the heximal value must have an even length
278: return null;
279: }
280: try {
281: prefix = prefix.substring(2);
282: // if prefix == 0xFFFFFFFFFFFFFFFF (allowed value),
283: // Long.parseLong would throw an exception, thus the
284: // string prefix has to be divided up
285: if (prefix.length() >= maxLength) {
286: Long.parseLong(prefix.substring(0, 7), 16);
287: Long.parseLong(prefix.substring(8), 16);
288: } else {
289: Long.parseLong(prefix, 16);
290: }
291: maxLength = 16;
292: } catch (NumberFormatException nfe) {
293: return null;
294: }
295: }
296:
297: if (prefix.length() > maxLength) {
298: return null;
299: }
300: }
301:
302: if (smsCount == null) {
303: info = new Object[2];
304: } else {
305: info = new Object[3];
306: try {
307: info[2] = Integer.valueOf(smsCount);
308: } catch (NumberFormatException nfe) {
309: return null;
310: }
311: }
312:
313: info[0] = msisdn;
314: info[1] = prefix;
315:
316: return info;
317: }
318:
319: /**
320: * Processes the given transaction, updates its state and returns the same
321: * transaction instance or a new one (an instance of
322: * a <code>Transaction</code> subclass), which is based on the old
323: * transaction, but adds more (adapter specific) information to it.
324: *
325: * @param transaction the transaction to be processed
326: * @return the transaction after processing
327: */
328: public Transaction process(Transaction transaction) {
329:
330: switch (transaction.getState()) {
331: case Transaction.ASSIGNED:
332: Thread thread = new PPSMSThread(transaction);
333: transaction.setWaiting(true);
334: thread.start();
335: break;
336:
337: default:
338: return super.process(transaction);
339: }
340:
341: return transaction;
342: }
343: }
|