001: package org.obe.runtime.tool;
002:
003: import org.apache.commons.logging.Log;
004: import org.apache.commons.logging.LogFactory;
005: import org.obe.client.api.repository.DocumentHandlerMetaData;
006: import org.obe.client.api.tool.ToolInvocation;
007:
008: import javax.activation.*;
009: import javax.swing.*;
010: import java.awt.*;
011: import java.awt.event.WindowAdapter;
012: import java.awt.event.WindowEvent;
013: import java.io.File;
014: import java.io.FileNotFoundException;
015: import java.io.IOException;
016: import java.io.Writer;
017: import java.lang.reflect.InvocationTargetException;
018: import java.net.MalformedURLException;
019: import java.net.URL;
020: import java.util.*;
021: import java.util.List;
022:
023: /**
024: * A tool agent that invokes a command upon an arbitrary document. The
025: * implementation uses the Java Activation Framework to invoke the requested
026: * command for the content type implied by the URL.
027: *
028: * @author Adrian Price
029: */
030: public class DocumentHandler extends AbstractToolAgent {
031: private static final Log _log = LogFactory
032: .getLog(DocumentHandler.class);
033:
034: private final DocumentHandlerMetaData _metadata;
035:
036: private final class ComponentViewer extends JFrame {
037: private static final int VERTICAL_MARGIN = 40;
038: private static final int HORIZONTAL_MARGIN = 15;
039:
040: ComponentViewer() {
041: super (_metadata.getTitle());
042: initCompViewer();
043: }
044:
045: ComponentViewer(String name) {
046: super (name);
047: initCompViewer();
048: }
049:
050: private void initCompViewer() {
051: setSize(_metadata.getWidth(), _metadata.getHeight());
052: getContentPane().setLayout(new BorderLayout());
053: if (_metadata.getStatus()) {
054: TextField statusBar = new TextField();
055: statusBar.setName(UpdateProcessAttributes.STATUS);
056: add(statusBar, BorderLayout.SOUTH);
057: }
058: if (_metadata.getScrollbars()) {
059: // TODO: set up scroll bars.
060: }
061: addWindowListener(new WindowAdapter() {
062: public void windowClosing(WindowEvent e) {
063: setVisible(false);
064: }
065: });
066: }
067:
068: // Display the bean as a component.
069: void setBean(Component bean) {
070: getContentPane().add(bean, BorderLayout.CENTER);
071: Dimension start_dim = bean.getPreferredSize();
072: if (start_dim.width != 0 && start_dim.height != 0) {
073: // this is what we do under normal conditions
074: start_dim.height += VERTICAL_MARGIN;
075: start_dim.width += HORIZONTAL_MARGIN;
076: setSize(start_dim);
077: bean.invalidate();
078: bean.validate();
079: bean.doLayout();
080: show();
081: } else {
082: // we get here if for some reason our child's
083: // getPref size needs to have its peer created
084: // first...
085: show();
086: start_dim = bean.getPreferredSize();
087: start_dim.height += VERTICAL_MARGIN;
088: start_dim.width += HORIZONTAL_MARGIN;
089: setSize(start_dim);
090: bean.validate();
091: }
092: setSize(getSize());// huh?
093: validate();
094: }
095: }
096:
097: // We can't trust the data source to tell us the correct MIME content type
098: // because an URLDataSource relies on the URLStream for the content type
099: // and the server might have been configured by an idiot. So, this is an
100: // idiot-proof URLDataSource.
101: public static class URLDataSourceExt extends URLDataSource {
102: String _contentType;
103:
104: public URLDataSourceExt(URL url) {
105: super (url);
106: _contentType = FileTypeMap.getDefaultFileTypeMap()
107: .getContentType(url.getPath());
108: }
109:
110: public String getContentType() {
111: return _contentType;
112: }
113: }
114:
115: public static class NativeCommand implements CommandObject,
116: Runnable {
117: private final String _cmdline;
118: private String _filename;
119: private String _contentType;
120:
121: public NativeCommand(String cmdline) {
122: _cmdline = cmdline;
123: }
124:
125: public void setCommandContext(String name,
126: DataHandler dataHandler) throws IOException {
127:
128: DataSource ds = dataHandler.getDataSource();
129: if (ds instanceof FileDataSource)
130: _filename = ((FileDataSource) ds).getFile()
131: .getCanonicalPath();
132: else if (ds instanceof URLDataSource)
133: _filename = ((URLDataSource) ds).getURL()
134: .toExternalForm();
135: _contentType = dataHandler.getContentType();
136: }
137:
138: public void run() {
139: Process process = null;
140: try {
141: String cmdline = _cmdline;
142: // TODO: Handle %{<parm-name>}
143: if (cmdline.indexOf("%s") > -1)
144: cmdline = cmdline.replaceAll("%s", _filename);
145: if (cmdline.indexOf("%t") > -1)
146: cmdline = cmdline.replaceAll("%t", _contentType);
147: process = Runtime.getRuntime().exec(cmdline);
148: process.waitFor();
149: } catch (InterruptedException e) {
150: process.destroy();
151: } catch (IOException e) {
152: _log.error(e);
153: }
154: }
155: }
156:
157: public static class NativeCommandInfo extends CommandInfo {
158: private final String _cmdline;
159:
160: public NativeCommandInfo(String cmdName, String cmdline) {
161: super (cmdName, NativeCommand.class.getName());
162: _cmdline = cmdline;
163: }
164:
165: public Object getCommandObject(DataHandler dataHandler,
166: ClassLoader classLoader) throws IOException,
167: ClassNotFoundException {
168:
169: CommandObject cmdObj = new NativeCommand(_cmdline);
170: cmdObj.setCommandContext(getCommandName(), dataHandler);
171: return cmdObj;
172: }
173: }
174:
175: public static class NativeMailcapCommandMap extends
176: MailcapCommandMap {
177: private static CommandMap _instance = new NativeMailcapCommandMap();
178: private MailcapCommandMap _delegate = (MailcapCommandMap) CommandMap
179: .getDefaultCommandMap();
180: private final Map _mce = new HashMap();
181:
182: private static class MailcapEntry {
183: final String mimeType;
184: final CommandInfo[] commandInfo;
185:
186: MailcapEntry(String rawtext) {
187: StringTokenizer strtok = new StringTokenizer(rawtext,
188: ";");
189: mimeType = strtok.nextToken().trim();
190: List cmds = new ArrayList(strtok.countTokens());
191: cmds.add(new NativeCommandInfo("view", strtok
192: .nextToken()));
193: while (strtok.hasMoreTokens()) {
194: String cmd = strtok.nextToken();
195: int eqpos = cmd.indexOf('=');
196: if (eqpos != -1) {
197: String cmdName = cmd.substring(0, eqpos - 1);
198: String cmdline = cmd.substring(eqpos + 1);
199: cmds
200: .add(new NativeCommandInfo(cmdName,
201: cmdline));
202: }
203: }
204: commandInfo = (CommandInfo[]) cmds
205: .toArray(new CommandInfo[cmds.size()]);
206: }
207: }
208:
209: public static CommandMap getDefaultNativeMailcapCommandMap() {
210: return _instance;
211: }
212:
213: private NativeMailcapCommandMap() {
214: _delegate = (MailcapCommandMap) CommandMap
215: .getDefaultCommandMap();
216: initializeNativeCommands();
217: }
218:
219: private void initializeNativeCommands() {
220: String[] mimeTypes = _delegate.getMimeTypes();
221: for (int i = 0; i < mimeTypes.length; i++) {
222: String mimeType = mimeTypes[i];
223: String[] nativeCmds = _delegate
224: .getNativeCommands(mimeType);
225: if (nativeCmds.length > 0) {
226: // For now, use only the first matching entry.
227: if (nativeCmds.length > 1) {
228: _log
229: .warn("Ignoring additional mailcap entries for "
230: + mimeType);
231: }
232: MailcapEntry mce = new MailcapEntry(nativeCmds[0]);
233: _mce.put(mimeType, mce);
234: }
235: }
236: }
237:
238: public synchronized CommandInfo getCommand(String mimeType,
239: String cmdName) {
240:
241: String key = cmdName == null ? "view" : cmdName;
242: CommandInfo ci = super .getCommand(mimeType, cmdName);
243: if (ci == null) {
244: MailcapEntry mc = (MailcapEntry) _mce.get(mimeType);
245: if (mc != null) {
246: CommandInfo[] cia = mc.commandInfo;
247: for (int i = 0; i < cia.length; i++) {
248: if (cia[i].getCommandName().equalsIgnoreCase(
249: key)) {
250: ci = cia[i];
251: break;
252: }
253: }
254: }
255: }
256: return ci;
257: }
258:
259: public synchronized CommandInfo[] getAllCommands(String mimeType) {
260: return getCommands(mimeType, super .getAllCommands(mimeType));
261: }
262:
263: public synchronized CommandInfo[] getPreferredCommands(
264: String mimeType) {
265: return getCommands(mimeType, super
266: .getPreferredCommands(mimeType));
267: }
268:
269: private CommandInfo[] getCommands(String mimeType,
270: CommandInfo[] cia) {
271: if (cia == null) {
272: // For now, do not differentiate preferred commands.
273: MailcapEntry mc = (MailcapEntry) _mce.get(mimeType);
274: if (mc != null)
275: cia = (CommandInfo[]) mc.commandInfo.clone();
276: }
277: return cia;
278: }
279: }
280:
281: private static void write(Writer writer, String key, int value)
282: throws IOException {
283:
284: writer.write(key);
285: writer.write('=');
286: writer.write(String.valueOf(value));
287: writer.write(',');
288: }
289:
290: private static void write(Writer writer, String key, boolean value)
291: throws IOException {
292:
293: writer.write(key);
294: writer.write('=');
295: writer.write(value ? "yes" : "no");
296: writer.write(',');
297: }
298:
299: public DocumentHandler(DocumentHandlerMetaData metadata) {
300: _metadata = metadata;
301: }
302:
303: public void renderInvocationScript(ToolInvocation ti, Writer writer)
304: throws IOException {
305:
306: writer.write("window.open(\"");
307: writer.write(ti.parameters[0].getValue().toString());
308: int n = ti.parameters.length;
309: if (n > 1) {
310: Object value = ti.parameters[1].getValue();
311: if (value != null) {
312: writer.write('#');
313: writer.write(value.toString());
314: }
315: if (n > 2) {
316: writer.write('?');
317: for (int i = 2; i < n; i++) {
318: if (i > 2)
319: writer.write('&');
320: writer.write(ti.parameters[i].getFormalParm()
321: .getId());
322: writer.write('=');
323: value = ti.parameters[i].getValue();
324: if (value != null)
325: writer.write(value.toString());
326: }
327: }
328: }
329: writer.write("\", \"");
330: if (_metadata.getTitle() != null)
331: writer.write(_metadata.getTitle());
332: writer.write("\", \"");
333: write(writer, "height", _metadata.getHeight());
334: write(writer, "width", _metadata.getWidth());
335: write(writer, UpdateProcessAttributes.STATUS, _metadata
336: .getStatus());
337: write(writer, "toolbar", _metadata.getToolbar());
338: write(writer, "menubar", _metadata.getMenubar());
339: write(writer, "location", _metadata.getLocation());
340: write(writer, "scrollbars", _metadata.getScrollbars());
341: writer.write("\");");
342: }
343:
344: protected int _invokeApplication(ToolInvocation ti)
345: throws InterruptedException, InvocationTargetException {
346:
347: // Create a data handler for the specified resource.
348: String resource = ti.parameters[0].getValue().toString();
349: DataSource ds;
350: try {
351: URL url = new URL(resource);
352: if (_log.isDebugEnabled())
353: _log
354: .debug("Looking for content hander for URL: "
355: + url);
356: ds = new URLDataSourceExt(url);
357: } catch (MalformedURLException e) {
358: File file = new File(resource);
359: if (!file.exists()) {
360: throw new InvocationTargetException(
361: new FileNotFoundException(resource));
362: }
363:
364: ds = new FileDataSource(file);
365: }
366: DataHandler dh = new DataHandler(ds);
367: dh.setCommandMap(NativeMailcapCommandMap
368: .getDefaultNativeMailcapCommandMap());
369: if (_log.isDebugEnabled())
370: _log.debug("MIME content type: " + dh.getContentType());
371:
372: // Retrieve information about the specified command.
373: String cmdName = _metadata.getCommandName();
374: if (cmdName == null)
375: cmdName = "view";
376: CommandInfo ci = dh.getCommand(cmdName);
377: if (ci == null)
378: throw new IllegalArgumentException("Command not found: "
379: + cmdName);
380:
381: // Execute the command.
382: Object bean = dh.getBean(ci);
383: if (bean instanceof Runnable) {
384: ((Runnable) bean).run();
385: } else if (bean instanceof Component) {
386: if (_log.isDebugEnabled())
387: _log.debug("Found content handler: " + bean);
388:
389: final ComponentViewer cv = new ComponentViewer();
390: cv.addWindowListener(new WindowAdapter() {
391: public void windowClosed(WindowEvent e) {
392: synchronized (cv) {
393: cv.notify();
394: }
395: }
396: });
397: cv.setBean((Component) bean);
398: synchronized (cv) {
399: cv.wait();
400: }
401: } else {
402: _log.warn("Unknown content handler: " + bean);
403: }
404: return 0;
405: }
406: }
|