001 /*
002 * Copyright 1997-2005 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.activation;
027
028 import java.util.*;
029 import java.io.*;
030 import java.net.*;
031 import com.sun.activation.registries.MailcapFile;
032 import com.sun.activation.registries.LogSupport;
033
034 /**
035 * MailcapCommandMap extends the CommandMap
036 * abstract class. It implements a CommandMap whose configuration
037 * is based on mailcap files
038 * (<A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A>).
039 * The MailcapCommandMap can be configured both programmatically
040 * and via configuration files.
041 * <p>
042 * <b>Mailcap file search order:</b><p>
043 * The MailcapCommandMap looks in various places in the user's
044 * system for mailcap file entries. When requests are made
045 * to search for commands in the MailcapCommandMap, it searches
046 * mailcap files in the following order:
047 * <p>
048 * <ol>
049 * <li> Programatically added entries to the MailcapCommandMap instance.
050 * <li> The file <code>.mailcap</code> in the user's home directory.
051 * <li> The file <<i>java.home</i>><code>/lib/mailcap</code>.
052 * <li> The file or resources named <code>META-INF/mailcap</code>.
053 * <li> The file or resource named <code>META-INF/mailcap.default</code>
054 * (usually found only in the <code>activation.jar</code> file).
055 * </ol>
056 * <p>
057 * <b>Mailcap file format:</b><p>
058 *
059 * Mailcap files must conform to the mailcap
060 * file specification (RFC 1524, <i>A User Agent Configuration Mechanism
061 * For Multimedia Mail Format Information</i>).
062 * The file format consists of entries corresponding to
063 * particular MIME types. In general, the specification
064 * specifies <i>applications</i> for clients to use when they
065 * themselves cannot operate on the specified MIME type. The
066 * MailcapCommandMap extends this specification by using a parameter mechanism
067 * in mailcap files that allows JavaBeans(tm) components to be specified as
068 * corresponding to particular commands for a MIME type.<p>
069 *
070 * When a mailcap file is
071 * parsed, the MailcapCommandMap recognizes certain parameter signatures,
072 * specifically those parameter names that begin with <code>x-java-</code>.
073 * The MailcapCommandMap uses this signature to find
074 * command entries for inclusion into its registries.
075 * Parameter names with the form <code>x-java-<name></code>
076 * are read by the MailcapCommandMap as identifying a command
077 * with the name <i>name</i>. When the <i>name</i> is <code>
078 * content-handler</code> the MailcapCommandMap recognizes the class
079 * signified by this parameter as a <i>DataContentHandler</i>.
080 * All other commands are handled generically regardless of command
081 * name. The command implementation is specified by a fully qualified
082 * class name of a JavaBean(tm) component. For example; a command for viewing
083 * some data can be specified as: <code>x-java-view=com.foo.ViewBean</code>.<p>
084 *
085 * When the command name is <code>fallback-entry</code>, the value of
086 * the command may be <code>true</code> or <code>false</code>. An
087 * entry for a MIME type that includes a parameter of
088 * <code>x-java-fallback-entry=true</code> defines fallback commands
089 * for that MIME type that will only be used if no non-fallback entry
090 * can be found. For example, an entry of the form <code>text/*; ;
091 * x-java-fallback-entry=true; x-java-view=com.sun.TextViewer</code>
092 * specifies a view command to be used for any text MIME type. This
093 * view command would only be used if a non-fallback view command for
094 * the MIME type could not be found.<p>
095 *
096 * MailcapCommandMap aware mailcap files have the
097 * following general form:<p>
098 * <code>
099 * # Comments begin with a '#' and continue to the end of the line.<br>
100 * <mime type>; ; <parameter list><br>
101 * # Where a parameter list consists of one or more parameters,<br>
102 * # where parameters look like: x-java-view=com.sun.TextViewer<br>
103 * # and a parameter list looks like: <br>
104 * text/plain; ; x-java-view=com.sun.TextViewer; x-java-edit=com.sun.TextEdit
105 * <br>
106 * # Note that mailcap entries that do not contain 'x-java' parameters<br>
107 * # and comply to RFC 1524 are simply ignored:<br>
108 * image/gif; /usr/dt/bin/sdtimage %s<br>
109 *
110 * </code>
111 * <p>
112 *
113 * @author Bart Calder
114 * @author Bill Shannon
115 *
116 * @since 1.6
117 */
118
119 public class MailcapCommandMap extends CommandMap {
120 /*
121 * We manage a collection of databases, searched in order.
122 * The default database is shared between all instances
123 * of this class.
124 * XXX - Can we safely share more databases between instances?
125 */
126 private static MailcapFile defDB = null;
127 private MailcapFile[] DB;
128 private static final int PROG = 0; // programmatically added entries
129
130 /**
131 * The default Constructor.
132 */
133 public MailcapCommandMap() {
134 super ();
135 List dbv = new ArrayList(5); // usually 5 or less databases
136 MailcapFile mf = null;
137 dbv.add(null); // place holder for PROG entry
138
139 LogSupport.log("MailcapCommandMap: load HOME");
140 try {
141 String user_home = System.getProperty("user.home");
142
143 if (user_home != null) {
144 String path = user_home + File.separator + ".mailcap";
145 mf = loadFile(path);
146 if (mf != null)
147 dbv.add(mf);
148 }
149 } catch (SecurityException ex) {
150 }
151
152 LogSupport.log("MailcapCommandMap: load SYS");
153 try {
154 // check system's home
155 String system_mailcap = System.getProperty("java.home")
156 + File.separator + "lib" + File.separator
157 + "mailcap";
158 mf = loadFile(system_mailcap);
159 if (mf != null)
160 dbv.add(mf);
161 } catch (SecurityException ex) {
162 }
163
164 LogSupport.log("MailcapCommandMap: load JAR");
165 // load from the app's jar file
166 loadAllResources(dbv, "META-INF/mailcap");
167
168 LogSupport.log("MailcapCommandMap: load DEF");
169 synchronized (MailcapCommandMap.class) {
170 // see if another instance has created this yet.
171 if (defDB == null)
172 defDB = loadResource("/META-INF/mailcap.default");
173 }
174
175 if (defDB != null)
176 dbv.add(defDB);
177
178 DB = new MailcapFile[dbv.size()];
179 DB = (MailcapFile[]) dbv.toArray(DB);
180 }
181
182 /**
183 * Load from the named resource.
184 */
185 private MailcapFile loadResource(String name) {
186 InputStream clis = null;
187 try {
188 clis = SecuritySupport.getResourceAsStream(this .getClass(),
189 name);
190 if (clis != null) {
191 MailcapFile mf = new MailcapFile(clis);
192 if (LogSupport.isLoggable())
193 LogSupport
194 .log("MailcapCommandMap: successfully loaded "
195 + "mailcap file: " + name);
196 return mf;
197 } else {
198 if (LogSupport.isLoggable())
199 LogSupport.log("MailcapCommandMap: not loading "
200 + "mailcap file: " + name);
201 }
202 } catch (IOException e) {
203 if (LogSupport.isLoggable())
204 LogSupport.log("MailcapCommandMap: can't load " + name,
205 e);
206 } catch (SecurityException sex) {
207 if (LogSupport.isLoggable())
208 LogSupport.log("MailcapCommandMap: can't load " + name,
209 sex);
210 } finally {
211 try {
212 if (clis != null)
213 clis.close();
214 } catch (IOException ex) {
215 } // ignore it
216 }
217 return null;
218 }
219
220 /**
221 * Load all of the named resource.
222 */
223 private void loadAllResources(List v, String name) {
224 boolean anyLoaded = false;
225 try {
226 URL[] urls;
227 ClassLoader cld = null;
228 // First try the "application's" class loader.
229 cld = SecuritySupport.getContextClassLoader();
230 if (cld == null)
231 cld = this .getClass().getClassLoader();
232 if (cld != null)
233 urls = SecuritySupport.getResources(cld, name);
234 else
235 urls = SecuritySupport.getSystemResources(name);
236 if (urls != null) {
237 if (LogSupport.isLoggable())
238 LogSupport.log("MailcapCommandMap: getResources");
239 for (int i = 0; i < urls.length; i++) {
240 URL url = urls[i];
241 InputStream clis = null;
242 if (LogSupport.isLoggable())
243 LogSupport.log("MailcapCommandMap: URL " + url);
244 try {
245 clis = SecuritySupport.openStream(url);
246 if (clis != null) {
247 v.add(new MailcapFile(clis));
248 anyLoaded = true;
249 if (LogSupport.isLoggable())
250 LogSupport.log("MailcapCommandMap: "
251 + "successfully loaded "
252 + "mailcap file from URL: "
253 + url);
254 } else {
255 if (LogSupport.isLoggable())
256 LogSupport.log("MailcapCommandMap: "
257 + "not loading mailcap "
258 + "file from URL: " + url);
259 }
260 } catch (IOException ioex) {
261 if (LogSupport.isLoggable())
262 LogSupport.log(
263 "MailcapCommandMap: can't load "
264 + url, ioex);
265 } catch (SecurityException sex) {
266 if (LogSupport.isLoggable())
267 LogSupport.log(
268 "MailcapCommandMap: can't load "
269 + url, sex);
270 } finally {
271 try {
272 if (clis != null)
273 clis.close();
274 } catch (IOException cex) {
275 }
276 }
277 }
278 }
279 } catch (Exception ex) {
280 if (LogSupport.isLoggable())
281 LogSupport.log("MailcapCommandMap: can't load " + name,
282 ex);
283 }
284
285 // if failed to load anything, fall back to old technique, just in case
286 if (!anyLoaded) {
287 if (LogSupport.isLoggable())
288 LogSupport.log("MailcapCommandMap: !anyLoaded");
289 MailcapFile mf = loadResource("/" + name);
290 if (mf != null)
291 v.add(mf);
292 }
293 }
294
295 /**
296 * Load from the named file.
297 */
298 private MailcapFile loadFile(String name) {
299 MailcapFile mtf = null;
300
301 try {
302 mtf = new MailcapFile(name);
303 } catch (IOException e) {
304 // e.printStackTrace();
305 }
306 return mtf;
307 }
308
309 /**
310 * Constructor that allows the caller to specify the path
311 * of a <i>mailcap</i> file.
312 *
313 * @param fileName The name of the <i>mailcap</i> file to open
314 * @exception IOException if the file can't be accessed
315 */
316 public MailcapCommandMap(String fileName) throws IOException {
317 this ();
318
319 if (LogSupport.isLoggable())
320 LogSupport.log("MailcapCommandMap: load PROG from "
321 + fileName);
322 if (DB[PROG] == null) {
323 DB[PROG] = new MailcapFile(fileName);
324 }
325 }
326
327 /**
328 * Constructor that allows the caller to specify an <i>InputStream</i>
329 * containing a mailcap file.
330 *
331 * @param is InputStream of the <i>mailcap</i> file to open
332 */
333 public MailcapCommandMap(InputStream is) {
334 this ();
335
336 LogSupport.log("MailcapCommandMap: load PROG");
337 if (DB[PROG] == null) {
338 try {
339 DB[PROG] = new MailcapFile(is);
340 } catch (IOException ex) {
341 // XXX - should throw it
342 }
343 }
344 }
345
346 /**
347 * Get the preferred command list for a MIME Type. The MailcapCommandMap
348 * searches the mailcap files as described above under
349 * <i>Mailcap file search order</i>.<p>
350 *
351 * The result of the search is a proper subset of available
352 * commands in all mailcap files known to this instance of
353 * MailcapCommandMap. The first entry for a particular command
354 * is considered the preferred command.
355 *
356 * @param mimeType the MIME type
357 * @return the CommandInfo objects representing the preferred commands.
358 */
359 public synchronized CommandInfo[] getPreferredCommands(
360 String mimeType) {
361 List cmdList = new ArrayList();
362 if (mimeType != null)
363 mimeType = mimeType.toLowerCase();
364
365 for (int i = 0; i < DB.length; i++) {
366 if (DB[i] == null)
367 continue;
368 Map cmdMap = DB[i].getMailcapList(mimeType);
369 if (cmdMap != null)
370 appendPrefCmdsToList(cmdMap, cmdList);
371 }
372
373 // now add the fallback commands
374 for (int i = 0; i < DB.length; i++) {
375 if (DB[i] == null)
376 continue;
377 Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
378 if (cmdMap != null)
379 appendPrefCmdsToList(cmdMap, cmdList);
380 }
381
382 CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()];
383 cmdInfos = (CommandInfo[]) cmdList.toArray(cmdInfos);
384
385 return cmdInfos;
386 }
387
388 /**
389 * Put the commands that are in the hash table, into the list.
390 */
391 private void appendPrefCmdsToList(Map cmdHash, List cmdList) {
392 Iterator verb_enum = cmdHash.keySet().iterator();
393
394 while (verb_enum.hasNext()) {
395 String verb = (String) verb_enum.next();
396 if (!checkForVerb(cmdList, verb)) {
397 List cmdList2 = (List) cmdHash.get(verb); // get the list
398 String className = (String) cmdList2.get(0);
399 cmdList.add(new CommandInfo(verb, className));
400 }
401 }
402 }
403
404 /**
405 * Check the cmdList to see if this command exists, return
406 * true if the verb is there.
407 */
408 private boolean checkForVerb(List cmdList, String verb) {
409 Iterator ee = cmdList.iterator();
410 while (ee.hasNext()) {
411 String enum_verb = (String) ((CommandInfo) ee.next())
412 .getCommandName();
413 if (enum_verb.equals(verb))
414 return true;
415 }
416 return false;
417 }
418
419 /**
420 * Get all the available commands in all mailcap files known to
421 * this instance of MailcapCommandMap for this MIME type.
422 *
423 * @param mimeType the MIME type
424 * @return the CommandInfo objects representing all the commands.
425 */
426 public synchronized CommandInfo[] getAllCommands(String mimeType) {
427 List cmdList = new ArrayList();
428 if (mimeType != null)
429 mimeType = mimeType.toLowerCase();
430
431 for (int i = 0; i < DB.length; i++) {
432 if (DB[i] == null)
433 continue;
434 Map cmdMap = DB[i].getMailcapList(mimeType);
435 if (cmdMap != null)
436 appendCmdsToList(cmdMap, cmdList);
437 }
438
439 // now add the fallback commands
440 for (int i = 0; i < DB.length; i++) {
441 if (DB[i] == null)
442 continue;
443 Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
444 if (cmdMap != null)
445 appendCmdsToList(cmdMap, cmdList);
446 }
447
448 CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()];
449 cmdInfos = (CommandInfo[]) cmdList.toArray(cmdInfos);
450
451 return cmdInfos;
452 }
453
454 /**
455 * Put the commands that are in the hash table, into the list.
456 */
457 private void appendCmdsToList(Map typeHash, List cmdList) {
458 Iterator verb_enum = typeHash.keySet().iterator();
459
460 while (verb_enum.hasNext()) {
461 String verb = (String) verb_enum.next();
462 List cmdList2 = (List) typeHash.get(verb);
463 Iterator cmd_enum = ((List) cmdList2).iterator();
464
465 while (cmd_enum.hasNext()) {
466 String cmd = (String) cmd_enum.next();
467 cmdList.add(new CommandInfo(verb, cmd));
468 // cmdList.add(0, new CommandInfo(verb, cmd));
469 }
470 }
471 }
472
473 /**
474 * Get the command corresponding to <code>cmdName</code> for the MIME type.
475 *
476 * @param mimeType the MIME type
477 * @param cmdName the command name
478 * @return the CommandInfo object corresponding to the command.
479 */
480 public synchronized CommandInfo getCommand(String mimeType,
481 String cmdName) {
482 if (mimeType != null)
483 mimeType = mimeType.toLowerCase();
484
485 for (int i = 0; i < DB.length; i++) {
486 if (DB[i] == null)
487 continue;
488 Map cmdMap = DB[i].getMailcapList(mimeType);
489 if (cmdMap != null) {
490 // get the cmd list for the cmd
491 List v = (List) cmdMap.get(cmdName);
492 if (v != null) {
493 String cmdClassName = (String) v.get(0);
494
495 if (cmdClassName != null)
496 return new CommandInfo(cmdName, cmdClassName);
497 }
498 }
499 }
500
501 // now try the fallback list
502 for (int i = 0; i < DB.length; i++) {
503 if (DB[i] == null)
504 continue;
505 Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
506 if (cmdMap != null) {
507 // get the cmd list for the cmd
508 List v = (List) cmdMap.get(cmdName);
509 if (v != null) {
510 String cmdClassName = (String) v.get(0);
511
512 if (cmdClassName != null)
513 return new CommandInfo(cmdName, cmdClassName);
514 }
515 }
516 }
517 return null;
518 }
519
520 /**
521 * Add entries to the registry. Programmatically
522 * added entries are searched before other entries.<p>
523 *
524 * The string that is passed in should be in mailcap
525 * format.
526 *
527 * @param mail_cap a correctly formatted mailcap string
528 */
529 public synchronized void addMailcap(String mail_cap) {
530 // check to see if one exists
531 LogSupport.log("MailcapCommandMap: add to PROG");
532 if (DB[PROG] == null)
533 DB[PROG] = new MailcapFile();
534
535 DB[PROG].appendToMailcap(mail_cap);
536 }
537
538 /**
539 * Return the DataContentHandler for the specified MIME type.
540 *
541 * @param mimeType the MIME type
542 * @return the DataContentHandler
543 */
544 public synchronized DataContentHandler createDataContentHandler(
545 String mimeType) {
546 if (LogSupport.isLoggable())
547 LogSupport
548 .log("MailcapCommandMap: createDataContentHandler for "
549 + mimeType);
550 if (mimeType != null)
551 mimeType = mimeType.toLowerCase();
552
553 for (int i = 0; i < DB.length; i++) {
554 if (DB[i] == null)
555 continue;
556 if (LogSupport.isLoggable())
557 LogSupport.log(" search DB #" + i);
558 Map cmdMap = DB[i].getMailcapList(mimeType);
559 if (cmdMap != null) {
560 List v = (List) cmdMap.get("content-handler");
561 if (v != null) {
562 String name = (String) v.get(0);
563 DataContentHandler dch = getDataContentHandler(name);
564 if (dch != null)
565 return dch;
566 }
567 }
568 }
569
570 // now try the fallback entries
571 for (int i = 0; i < DB.length; i++) {
572 if (DB[i] == null)
573 continue;
574 if (LogSupport.isLoggable())
575 LogSupport.log(" search fallback DB #" + i);
576 Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
577 if (cmdMap != null) {
578 List v = (List) cmdMap.get("content-handler");
579 if (v != null) {
580 String name = (String) v.get(0);
581 DataContentHandler dch = getDataContentHandler(name);
582 if (dch != null)
583 return dch;
584 }
585 }
586 }
587 return null;
588 }
589
590 private DataContentHandler getDataContentHandler(String name) {
591 if (LogSupport.isLoggable())
592 LogSupport.log(" got content-handler");
593 if (LogSupport.isLoggable())
594 LogSupport.log(" class " + name);
595 try {
596 ClassLoader cld = null;
597 // First try the "application's" class loader.
598 cld = SecuritySupport.getContextClassLoader();
599 if (cld == null)
600 cld = this .getClass().getClassLoader();
601 Class cl = null;
602 try {
603 cl = cld.loadClass(name);
604 } catch (Exception ex) {
605 // if anything goes wrong, do it the old way
606 cl = Class.forName(name);
607 }
608 if (cl != null) // XXX - always true?
609 return (DataContentHandler) cl.newInstance();
610 } catch (IllegalAccessException e) {
611 if (LogSupport.isLoggable())
612 LogSupport.log("Can't load DCH " + name, e);
613 } catch (ClassNotFoundException e) {
614 if (LogSupport.isLoggable())
615 LogSupport.log("Can't load DCH " + name, e);
616 } catch (InstantiationException e) {
617 if (LogSupport.isLoggable())
618 LogSupport.log("Can't load DCH " + name, e);
619 }
620 return null;
621 }
622
623 /**
624 * Get all the MIME types known to this command map.
625 *
626 * @return array of MIME types as strings
627 * @since JAF 1.1
628 */
629 public synchronized String[] getMimeTypes() {
630 List mtList = new ArrayList();
631
632 for (int i = 0; i < DB.length; i++) {
633 if (DB[i] == null)
634 continue;
635 String[] ts = DB[i].getMimeTypes();
636 if (ts != null) {
637 for (int j = 0; j < ts.length; j++) {
638 // eliminate duplicates
639 if (!mtList.contains(ts[j]))
640 mtList.add(ts[j]);
641 }
642 }
643 }
644
645 String[] mts = new String[mtList.size()];
646 mts = (String[]) mtList.toArray(mts);
647
648 return mts;
649 }
650
651 /**
652 * Get the native commands for the given MIME type.
653 * Returns an array of strings where each string is
654 * an entire mailcap file entry. The application
655 * will need to parse the entry to extract the actual
656 * command as well as any attributes it needs. See
657 * <A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A>
658 * for details of the mailcap entry syntax. Only mailcap
659 * entries that specify a view command for the specified
660 * MIME type are returned.
661 *
662 * @return array of native command entries
663 * @since JAF 1.1
664 */
665 public synchronized String[] getNativeCommands(String mimeType) {
666 List cmdList = new ArrayList();
667 if (mimeType != null)
668 mimeType = mimeType.toLowerCase();
669
670 for (int i = 0; i < DB.length; i++) {
671 if (DB[i] == null)
672 continue;
673 String[] cmds = DB[i].getNativeCommands(mimeType);
674 if (cmds != null) {
675 for (int j = 0; j < cmds.length; j++) {
676 // eliminate duplicates
677 if (!cmdList.contains(cmds[j]))
678 cmdList.add(cmds[j]);
679 }
680 }
681 }
682
683 String[] cmds = new String[cmdList.size()];
684 cmds = (String[]) cmdList.toArray(cmds);
685
686 return cmds;
687 }
688
689 /**
690 * for debugging...
691 *
692 public static void main(String[] argv) throws Exception {
693 MailcapCommandMap map = new MailcapCommandMap();
694 CommandInfo[] cmdInfo;
695
696 cmdInfo = map.getPreferredCommands(argv[0]);
697 System.out.println("Preferred Commands:");
698 for (int i = 0; i < cmdInfo.length; i++)
699 System.out.println("Command " + cmdInfo[i].getCommandName() + " [" +
700 cmdInfo[i].getCommandClass() + "]");
701 cmdInfo = map.getAllCommands(argv[0]);
702 System.out.println();
703 System.out.println("All Commands:");
704 for (int i = 0; i < cmdInfo.length; i++)
705 System.out.println("Command " + cmdInfo[i].getCommandName() + " [" +
706 cmdInfo[i].getCommandClass() + "]");
707 DataContentHandler dch = map.createDataContentHandler(argv[0]);
708 if (dch != null)
709 System.out.println("DataContentHandler " +
710 dch.getClass().toString());
711 System.exit(0);
712 }
713 */
714 }
|