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.midp.installer;
028:
029: import java.io.IOException;
030:
031: import javax.microedition.io.ConnectionNotFoundException;
032: import javax.microedition.lcdui.*;
033: import javax.microedition.midlet.MIDlet;
034:
035: import com.sun.midp.i18n.Resource;
036: import com.sun.midp.i18n.ResourceConstants;
037:
038: import com.sun.midp.midlet.MIDletSuite;
039:
040: import com.sun.midp.midletsuite.MIDletInfo;
041: import com.sun.midp.midletsuite.MIDletSuiteStorage;
042:
043: import com.sun.midp.security.Permissions;
044:
045: import com.sun.midp.log.Logging;
046: import com.sun.midp.log.LogChannels;
047:
048: /**
049: * Installs/Updates a test suite, runs the first MIDlet in the suite in a loop
050: * until the new version of the suite is not found, then removes the suite.
051: * <p>
052: * The MIDlet uses these application properties as arguments: </p>
053: * <ol>
054: * <li>arg-0: URL for the test suite
055: * <li>arg-1: Used to override the default domain used when installing
056: * an unsigned suite. The default is maximum to allow the runtime API tests
057: * be performed automatically without tester interaction. The domain name
058: * may be followed by a colon and a list of permissions that must be allowed
059: * even if they are not listed in the MIDlet-Permissions attribute in the
060: * application descriptor file. Instead of the list a keyword "all" can be
061: * specified indicating that all permissions must be allowed, for example:
062: * operator:all.
063: * </ol>
064: * <p>
065: * If arg-0 is not given then a form will be used to query the tester for
066: * the arguments.</p>
067: */
068: class AutoTesterBase extends MIDlet implements CommandListener,
069: Runnable {
070:
071: /** Standard timeout for alerts. */
072: static final int ALERT_TIMEOUT = 5000;
073: /** Contains the default URL. */
074: static final String defaultUrl = "http://";
075:
076: /** Display for this MIDlet. */
077: Display display;
078: /** Parameter form if there is not URL parameter given. */
079: Form parameterForm;
080: /** Contains the URL the user typed in. */
081: TextField urlTextField;
082: /** Contains the domain the user typed in. */
083: TextField domainTextField;
084: /** Command object for "Exit" command in the URL screen. */
085: Command endCmd = new Command(Resource
086: .getString(ResourceConstants.EXIT), Command.EXIT, 1);
087: /** Command object for URL screen start testing. */
088: Command testCmd = new Command(Resource
089: .getString(ResourceConstants.GO), Command.SCREEN, 1);
090: /** URL of the test suite. */
091: String url;
092: /** Security domain to assign to unsigned suites. */
093: String domain = Permissions.UNIDENTIFIED_DOMAIN_BINDING;
094: /** MIDlet suite storage object. */
095: MIDletSuiteStorage midletSuiteStorage;
096: /** The installer. */
097: Installer installer;
098: /** How many iterations to run the suite */
099: int loopCount = -1;
100:
101: /** The InstallListener to use when creating the Installer. */
102: protected InstallListener installListener;
103:
104: /**
105: * Create and initialize a new auto tester MIDlet.
106: */
107: AutoTesterBase() {
108: display = Display.getDisplay(this );
109:
110: // The arg-<n> properties are generic command arguments
111: url = getAppProperty("arg-0");
112: if (url != null) {
113: // URL given as a argument, look for a domain arg and then start
114: String arg1 = getAppProperty("arg-1");
115:
116: boolean hasLoopCount = false;
117: if (arg1 != null) {
118: // this can be domain or loop count
119: try {
120: loopCount = Integer.parseInt(arg1);
121: hasLoopCount = true;
122: } catch (NumberFormatException e) {
123: // then its domain
124: domain = arg1;
125: }
126:
127: if (!hasLoopCount) {
128: String arg2 = getAppProperty("arg-2");
129: if (arg2 != null) {
130: try {
131: loopCount = Integer.parseInt(arg2);
132: } catch (NumberFormatException e) {
133: // just ignore
134: }
135: }
136: }
137: }
138: }
139: }
140:
141: /**
142: * Start.
143: */
144: public void startApp() {
145: // Avoid competing for foreground with Test MIDlet
146: display.setCurrent(null);
147: notifyPaused();
148: }
149:
150: /**
151: * Pause; there are no resources that need to be released.
152: */
153: public void pauseApp() {
154: }
155:
156: /**
157: * Destroy cleans up.
158: *
159: * @param unconditional is ignored; this object always
160: * destroys itself when requested.
161: */
162: public void destroyApp(boolean unconditional) {
163: }
164:
165: /**
166: * Respond to a command issued on any Screen.
167: *
168: * @param c command activated by the user
169: * @param s the Displayable the command was on.
170: */
171: public void commandAction(Command c, Displayable s) {
172: if (c == testCmd) {
173: getURLTextAndTest();
174: } else if (c == endCmd || c == Alert.DISMISS_COMMAND) {
175: // goto back to the manager midlet
176: notifyDestroyed();
177: }
178: }
179:
180: /**
181: * Ask the user for the URL.
182: */
183: void getUrl() {
184: try {
185: parameterForm = new Form(
186: Resource
187: .getString(ResourceConstants.AMS_AUTO_TESTER_TESTSUITE_PARAM));
188:
189: urlTextField = new TextField(Resource
190: .getString(ResourceConstants.AMS_AUTO_TESTER_URL),
191: defaultUrl, 1024, TextField.ANY);
192: urlTextField.setLayout(Item.LAYOUT_NEWLINE_AFTER
193: | Item.LAYOUT_2);
194: parameterForm.append(urlTextField);
195:
196: domainTextField = new TextField(
197: Resource
198: .getString(ResourceConstants.AMS_AUTO_TESTER_UNSIGN_SECURITY_DOMAIN),
199: domain, 1024, TextField.ANY);
200: domainTextField.setLayout(Item.LAYOUT_NEWLINE_AFTER
201: | Item.LAYOUT_2);
202: parameterForm.append(domainTextField);
203:
204: parameterForm.addCommand(endCmd);
205: parameterForm.addCommand(testCmd);
206: parameterForm.setCommandListener(this );
207:
208: display.setCurrent(parameterForm);
209: } catch (Exception ex) {
210: displayException(Resource
211: .getString(ResourceConstants.EXCEPTION), ex
212: .toString());
213: }
214: }
215:
216: /**
217: * Save the URL setting the user entered in to the urlTextBox.
218: */
219: void getURLTextAndTest() {
220: url = urlTextField.getString();
221:
222: if (url == null || url.length() == 0) {
223: Alert a = new Alert(
224: Resource.getString(ResourceConstants.ERROR),
225: Resource
226: .getString(ResourceConstants.AMS_AUTO_TESTER_ERROR_URL_MSG),
227: null, AlertType.ERROR);
228: a.setTimeout(ALERT_TIMEOUT);
229: display.setCurrent(a, parameterForm);
230: return;
231: }
232:
233: domain = domainTextField.getString();
234:
235: if (domain == null || domain.length() == 0) {
236: Alert a = new Alert(
237: Resource.getString(ResourceConstants.ERROR),
238: Resource
239: .getString(ResourceConstants.AMS_AUTO_TESTER_ERROR_SECURITY_DOMAIN_MSG),
240: null, AlertType.ERROR);
241: a.setTimeout(ALERT_TIMEOUT);
242: display.setCurrent(a, parameterForm);
243: return;
244: }
245:
246: startBackgroundTester();
247: }
248:
249: /**
250: * Start the background tester.
251: */
252: void startBackgroundTester() {
253: midletSuiteStorage = MIDletSuiteStorage.getMIDletSuiteStorage();
254:
255: installer = new HttpInstaller();
256: if (domain != null) {
257: String additionalPermissions = null;
258: int index = domain.indexOf(":");
259: int len = domain.length();
260:
261: if (index > 0 && index + 1 < len) {
262: additionalPermissions = domain
263: .substring(index + 1, len);
264: domain = domain.substring(0, index);
265: }
266:
267: installer.setUnsignedSecurityDomain(domain);
268: installer.setExtraPermissions(additionalPermissions);
269: }
270:
271: new Thread(this ).start();
272: }
273:
274: /** Run the installer. */
275: public void run() {
276: }
277:
278: /**
279: * Handles an installer exceptions.
280: *
281: * @param suiteId ID of the suite being installed, can be null
282: * @param ex exception to handle
283: */
284: void handleInstallerException(int suiteId, Throwable ex) {
285: String message = null;
286:
287: if (ex instanceof InvalidJadException) {
288: InvalidJadException ije = (InvalidJadException) ex;
289:
290: /*
291: * The server will signal the end of testing with not found
292: * status. However print out the JAD not found error if this
293: * is the first download. (suiteId == null)
294: */
295: int reason = ije.getReason();
296: if ((reason != InvalidJadException.JAD_NOT_FOUND && reason != InvalidJadException.JAD_SERVER_NOT_FOUND)
297: || suiteId == MIDletSuite.UNUSED_SUITE_ID) {
298: message = "** Error installing suite (" + reason
299: + "): " + messageForInvalidJadException(ije);
300: }
301: } else if (ex instanceof IOException) {
302: message = "** I/O Error installing suite: "
303: + ex.getMessage();
304: } else {
305: message = "** Error installing suite: " + ex.toString();
306: }
307:
308: if (message != null) {
309: displayException(Resource
310: .getString(ResourceConstants.ERROR), message);
311:
312: long start = System.currentTimeMillis();
313: long time_left = ALERT_TIMEOUT;
314:
315: while (time_left > 0) {
316: try {
317: Thread.sleep(time_left);
318: time_left = 0;
319: } catch (InterruptedException ie) {
320: long tmp = System.currentTimeMillis();
321: time_left -= (tmp - start);
322: start = tmp;
323: }
324: }
325: }
326: }
327:
328: /**
329: * Display an exception to the user, with a done command.
330: *
331: * @param title exception form's title
332: * @param message exception message
333: */
334: void displayException(String title, String message) {
335: Alert a = new Alert(title, message, null, AlertType.ERROR);
336:
337: // This application must log always.
338: Logging.report(Logging.CRITICAL, LogChannels.LC_CORE, message);
339: a.setTimeout(ALERT_TIMEOUT);
340: a.setCommandListener(this );
341:
342: display.setCurrent(a);
343: }
344:
345: /**
346: * Returns the class name of the first MIDlet of the newly installed
347: * suite.
348: *
349: * @param suiteId ID of the MIDlet Suite
350: * @param midletSuiteStorage MIDlet suite storage to look up properties
351: *
352: * @return an object with the class name and display name of
353: * the suite's MIDlet-1 property
354: */
355: static MIDletInfo getFirstMIDletOfSuite(int suiteId,
356: MIDletSuiteStorage midletSuiteStorage) {
357: MIDletSuite ms = null;
358: String name = null;
359:
360: try {
361: ms = midletSuiteStorage.getMIDletSuite(suiteId, false);
362: name = ms.getProperty("MIDlet-1");
363: } catch (Exception e) {
364: throw new RuntimeException("midlet properties corrupted");
365: } finally {
366: if (ms != null) {
367: ms.close();
368: }
369: }
370:
371: if (name == null) {
372: throw new RuntimeException("MIDlet-1 missing");
373: }
374:
375: // found the entry now parse out the class name, and display name
376: return new MIDletInfo(name);
377: }
378:
379: /**
380: * Returns the associated message for the given exception.
381: * This function is here instead of in the exception its self because
382: * it not need on devices, it needed only on development platforms that
383: * have command line interface.
384: *
385: * @param ije reason reason code for the exception
386: *
387: * @return associated message for the given reason
388: */
389: static String messageForInvalidJadException(InvalidJadException ije) {
390: switch (ije.getReason()) {
391: case InvalidJadException.MISSING_PROVIDER_CERT:
392: case InvalidJadException.MISSING_SUITE_NAME:
393: case InvalidJadException.MISSING_VENDOR:
394: case InvalidJadException.MISSING_VERSION:
395: case InvalidJadException.MISSING_JAR_URL:
396: case InvalidJadException.MISSING_JAR_SIZE:
397: case InvalidJadException.MISSING_CONFIGURATION:
398: case InvalidJadException.MISSING_PROFILE:
399: return "A required attribute is missing";
400:
401: case InvalidJadException.SUITE_NAME_MISMATCH:
402: case InvalidJadException.VERSION_MISMATCH:
403: case InvalidJadException.VENDOR_MISMATCH:
404: return "A required suite ID attribute in the JAR manifest "
405: + "do not match the one in the JAD";
406:
407: case InvalidJadException.ATTRIBUTE_MISMATCH:
408: return "The value for "
409: + ije.getExtraData()
410: + " in the "
411: + "trusted JAR manifest did not match the one in the JAD";
412:
413: case InvalidJadException.CORRUPT_PROVIDER_CERT:
414: return "The content provider certificate cannot be decoded.";
415:
416: case InvalidJadException.UNKNOWN_CA:
417: return "The content provider certificate issuer "
418: + ije.getExtraData() + " is unknown.";
419:
420: case InvalidJadException.INVALID_PROVIDER_CERT:
421: return "The signature of the content provider certificate "
422: + "is invalid.";
423:
424: case InvalidJadException.CORRUPT_SIGNATURE:
425: return "The JAR signature cannot be decoded.";
426:
427: case InvalidJadException.INVALID_SIGNATURE:
428: return "The signature of the JAR is invalid.";
429:
430: case InvalidJadException.UNSUPPORTED_CERT:
431: return "The content provider certificate is not a supported "
432: + "version.";
433:
434: case InvalidJadException.EXPIRED_PROVIDER_CERT:
435: return "The content provider certificate is expired.";
436:
437: case InvalidJadException.EXPIRED_CA_KEY:
438: return "The public key of " + ije.getExtraData()
439: + " has expired.";
440:
441: case InvalidJadException.JAR_SIZE_MISMATCH:
442: return "The Jar downloaded was not the size in the JAD";
443:
444: case InvalidJadException.OLD_VERSION:
445: return "The application is an older version of one that is "
446: + "already installed";
447:
448: case InvalidJadException.NEW_VERSION:
449: return "The application is an newer version of one that is "
450: + "already installed";
451:
452: case InvalidJadException.INVALID_JAD_URL:
453: return "The JAD URL is invalid";
454:
455: case InvalidJadException.JAD_SERVER_NOT_FOUND:
456: return "JAD server not found";
457:
458: case InvalidJadException.JAD_NOT_FOUND:
459: return "JAD not found";
460:
461: case InvalidJadException.INVALID_JAR_URL:
462: return "The JAR URL in the JAD is invalid: "
463: + ije.getExtraData();
464:
465: case InvalidJadException.JAR_SERVER_NOT_FOUND:
466: return "JAR server not found: " + ije.getExtraData();
467:
468: case InvalidJadException.JAR_NOT_FOUND:
469: return "JAR not found: " + ije.getExtraData();
470:
471: case InvalidJadException.CORRUPT_JAR:
472: return "Corrupt JAR, error while reading: "
473: + ije.getExtraData();
474:
475: case InvalidJadException.INVALID_JAR_TYPE:
476: if (ije.getExtraData() != null) {
477: return "JAR did not have the correct media type, it had "
478: + ije.getExtraData();
479: }
480:
481: return "The server did not have a resource with an "
482: + "acceptable media type for the JAR URL. (code 406)";
483:
484: case InvalidJadException.INVALID_JAD_TYPE:
485: if (ije.getExtraData() != null) {
486: String temp = ije.getExtraData();
487:
488: if (temp.length() == 0) {
489: return "JAD did not have a media type";
490: }
491:
492: return "JAD did not have the correct media type, it had "
493: + temp;
494: }
495:
496: /*
497: * Should not happen, the accept field is not send
498: * when getting the JAD.
499: */
500: return "The server did not have a resource with an "
501: + "acceptable media type for the JAD URL. (code 406)";
502:
503: case InvalidJadException.INVALID_KEY:
504: return "The attribute key [" + ije.getExtraData()
505: + "] is not in the proper format";
506:
507: case InvalidJadException.INVALID_VALUE:
508: return "The value for attribute " + ije.getExtraData()
509: + " is not in the proper format";
510:
511: case InvalidJadException.INSUFFICIENT_STORAGE:
512: return "There is insufficient storage to install this suite";
513:
514: case InvalidJadException.UNAUTHORIZED:
515: return "Authentication required or failed";
516:
517: case InvalidJadException.JAD_MOVED:
518: return "The JAD to be installed is for an existing suite, "
519: + "but not from the same domain as the existing one: "
520: + ije.getExtraData();
521:
522: case InvalidJadException.CANNOT_AUTH:
523: return "Cannot authenticate with the server, unsupported scheme";
524:
525: case InvalidJadException.DEVICE_INCOMPATIBLE:
526: return "Either the configuration or profile is not supported.";
527:
528: case InvalidJadException.ALREADY_INSTALLED:
529: return "The JAD matches a version of a suite already installed.";
530:
531: case InvalidJadException.AUTHORIZATION_FAILURE:
532: return "The suite is not authorized for "
533: + ije.getExtraData();
534:
535: case InvalidJadException.PUSH_DUP_FAILURE:
536: return "The suite is in conflict with another application "
537: + "listening for network data on "
538: + ije.getExtraData();
539:
540: case InvalidJadException.PUSH_FORMAT_FAILURE:
541: return "Push attribute in incorrectly formated: "
542: + ije.getExtraData();
543:
544: case InvalidJadException.PUSH_PROTO_FAILURE:
545: return "Connection in push attribute is not supported: "
546: + ije.getExtraData();
547:
548: case InvalidJadException.PUSH_CLASS_FAILURE:
549: return "The class in push attribute not in a MIDlet-<n> "
550: + "attribute: " + ije.getExtraData();
551:
552: case InvalidJadException.TRUSTED_OVERWRITE_FAILURE:
553: return "Cannot update a trusted suite with an untrusted "
554: + "version";
555:
556: case InvalidJadException.INVALID_CONTENT_HANDLER:
557: return "Content handler attribute(s) incorrectly formatted: "
558: + ije.getExtraData();
559:
560: case InvalidJadException.CONTENT_HANDLER_CONFLICT:
561: return "Content handler would conflict with another handler: "
562: + ije.getExtraData();
563:
564: case InvalidJadException.CA_DISABLED:
565: return "The application can't be authorized because "
566: + ije.getExtraData() + " is disabled.";
567:
568: case InvalidJadException.UNSUPPORTED_CHAR_ENCODING:
569: return "Unsupported character encoding: "
570: + ije.getExtraData();
571: }
572:
573: return ije.getMessage();
574: }
575: }
|