001    /*
002      Copyright (C) 2002-2003 Renaud Pawlak <renaud@aopsys.com>, 
003                              Laurent Martelli <laurent@aopsys.com>
004      
005      This program is free software; you can redistribute it and/or modify
006      it under the terms of the GNU Lesser General Public License as
007      published by the Free Software Foundation; either version 2 of the
008      License, or (at your option) any later version.
009    
010      This program is distributed in the hope that it will be useful, but
011      WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013      Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public
016      License along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
018      USA */
019    
020    package org.objectweb.jac.aspects.gui.web;
021    
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.io.OutputStream;
025    import java.io.OutputStreamWriter;
026    import java.io.Writer;
027    import java.util.Arrays;
028    import java.util.Collection;
029    import java.util.HashSet;
030    import java.util.Hashtable;
031    import java.util.Iterator;
032    import java.util.Map;
033    import javax.servlet.http.HttpServletResponse;
034    import org.apache.log4j.Logger;
035    import org.mortbay.http.HttpContext;
036    import org.mortbay.http.HttpServer;
037    import org.mortbay.http.handler.ResourceHandler;
038    import org.mortbay.jetty.servlet.ServletHandler;
039    import org.mortbay.util.InetAddrPort;
040    import org.mortbay.util.MultiException;
041    import org.objectweb.jac.aspects.gui.*;
042    import org.objectweb.jac.aspects.session.SessionAC;
043    import org.objectweb.jac.core.Collaboration;
044    import org.objectweb.jac.core.rtti.AbstractMethodItem;
045    import org.objectweb.jac.lib.Attachment;
046    import org.objectweb.jac.util.ExtArrays;
047    import org.objectweb.jac.util.ExtBoolean;
048    import org.objectweb.jac.util.Strings;
049    
050    /**
051     * This class provides a server for web clients using a thin client
052     * protocol and implements the <code>Display</code> interface to
053     * assume data inputs and outputs between JAC objects and web clients.
054     *
055     * <p>This inferface is typically used by Java web clients and more
056     * specifically by servlets. The implementation provided by JAC is
057     * <code>JacServlet</code>.
058     *
059     * @author <a href="mailto:renaud@cnam.fr">Renaud Pawlak</a>
060     * @author <a href="mailto:laurent@aopsys.com">Laurent Martelli</a> 
061     */
062    public class WebDisplay implements CustomizedDisplay {
063        static Logger logger = Logger.getLogger("display");
064        static Logger loggerWeb = Logger.getLogger("web");
065        static Logger loggerViews = Logger.getLogger("web.views");
066        static Logger loggerRequest = Logger.getLogger("web.request");
067        static Logger loggerHtml = Logger.getLogger("web.html");
068        static Logger loggerEditor = Logger.getLogger("gui.editor");
069    
070    
071        ViewFactory factory;
072        // customizedID -> customized
073        Hashtable frames = new Hashtable();
074    
075        /* The session */
076        Session session;
077    
078        /**
079         * Create a new web display
080         * @param factory the ViewFactory of the display
081         * @param sessionId the sessionId of the display
082         */
083        public WebDisplay(ViewFactory factory, String sessionId) {
084            this.factory = factory;
085            session = new Session(sessionId);
086            Collaboration.get().addAttribute( 
087                SessionAC.SESSION_ID, sessionId );
088        }
089    
090        public static final String RESPONSE = "gui.web.response";
091        public static final String REQUEST = "gui.web.request";
092        public static final String ON_ENTER_ACTION = "GuiAC.web.ON_ENTER_ACTION";
093    
094        /**
095         * Set the servlet response for the current collaboration
096         * @param response the servlet response object
097         */
098        public static void setResponse(HttpServletResponse response) {
099            Collaboration.get().addAttribute(RESPONSE,response);
100        }
101        /**
102         * Returnsx the servlet response of the current collaboration
103         * @return the servlet response of the current collaboration
104         */
105        public static HttpServletResponse getResponse() {
106            return (HttpServletResponse)Collaboration.get().getAttribute(RESPONSE);
107        }
108    
109        /**
110         * Sets the current JacRequest in the context.
111         * @param request the JacRequets
112         */ 
113        public static void setRequest(JacRequest request) {
114            loggerRequest.debug("Setting request for "+Strings.hex(Collaboration.get())+
115                                ": "+request);
116            Collaboration.get().addAttribute(REQUEST,request);
117        }
118    
119        /**
120         * Returns the current JacRequest contained in the context.
121         * @return the current JacRequest contained in the context
122         */
123        public static JacRequest getRequest() {
124            return (JacRequest)Collaboration.get().getAttribute(REQUEST);
125        }
126    
127        protected static void grabResponse() {
128            setResponse(null);
129            //getRequest().setResponse();
130            //setResponse(null);
131        }
132    
133        /**
134         * Read values of field editors from http request
135         * @param editors field editors for which to read the value
136         * @param request the http request where to read values from
137         * @param commit if true, commit() will be called on the field
138         * editors after readValue() 
139         * @see HTMLEditor#readValue(Object)
140         * @see HTMLEditor#commit()
141         */
142        public static void readValues(DisplayContext editors, JacRequest request, 
143                                      boolean commit) 
144        {
145            loggerEditor.debug("reading values from context "+editors);
146            Iterator i = editors.getEditors().iterator();
147            while (i.hasNext()) {
148                View editor = (View)i.next();
149                try {
150                    Object parameter = request.getParameter(editor.getLabel());
151                    loggerEditor.debug("reading value for "+editor.getLabel());
152                    if (editor instanceof JacRequestReader) {
153                        ((JacRequestReader)editor).readValue(request);
154                    } else {
155                        if (((FieldEditor)editor).isEnabled()) {
156                            HTMLEditor htmlEditor = (HTMLEditor)editor;
157                            if (htmlEditor.readValue(parameter) && commit) {
158                                try {
159                                    htmlEditor.commit();
160                                } catch (Exception e) {
161                                    throw new CommitException(
162                                        e,
163                                        ((FieldEditor)editor).getSubstance(),
164                                        ((FieldEditor)editor).getField());
165                                }
166                            }
167                        }
168                    }
169                } catch (CommitException e) {
170                    throw e;
171                } catch(Exception e) {
172                    loggerEditor.error("Failed to readValue for "+editor+"/"+editor.getLabel(),e);
173                }
174            }
175        }
176    
177        public static void readValuesAndRefresh(
178            DisplayContext editors, JacRequest request, 
179            boolean commit) 
180        {
181            try {
182                readValues(editors,request,commit);
183                editors.getDisplay().refresh();
184            } catch(CommitException e) {
185                editors.getDisplay().showError(
186                    "Commit error",
187                    "Failed to set value of "+e.getField()+
188                    " on "+GuiAC.toString(e.getObject())+
189                    ": "+e.getNested().getMessage());
190            }
191        }
192    
193        public void fullRefresh() {
194            Iterator it = frames.entrySet().iterator();
195            while(it.hasNext()) {
196                Map.Entry entry = (Map.Entry)it.next();
197                View frame = (View)entry.getValue();
198                CustomizedGUI customized = ((CustomizedView)frame).getCustomizedGUI();
199                frame.close(true);
200                View newframe = factory.createView(
201                    customized.getTitle(),"Customized",
202                    new Object[] {customized,null},
203                    new DisplayContext(this,null));
204                logger.debug("frame created "+newframe);
205                frames.put(entry.getKey(),newframe);
206                session.newRequest(new Request(newframe));
207            }
208        }
209    
210        public void showCustomized(String id, Object object) {
211            showCustomized(id,object,null);
212        }
213    
214        public void showCustomized(String id, Object object, Map panels) {
215            logger.debug("showCustomized("+id+","+object+")");
216            try {
217                CustomizedGUI customized = (CustomizedGUI)object;
218                if (frames.get(id)!=null) {
219                    if (panels!=null) {
220                        CustomizedView frame = (CustomizedView)frames.get(id);
221                        GenericFactory.initCustomized(frame.getFactory(), 
222                                                      frame.getContext(), 
223                                                      frame.getPanelView(), 
224                                                      customized, panels);         
225                    }
226                    refresh();
227                } else {
228                    HTMLViewer frame = (HTMLViewer)factory.createView(
229                        customized.getTitle(),"Customized",
230                        new Object[] {customized,panels},
231                        new DisplayContext(this,null));
232                    logger.debug("frame created");
233                    frames.put(id,frame);
234                    session.newRequest(new Request((View)frame));
235                    refresh();
236                }
237            } catch(Exception e) {
238                e.printStackTrace();
239                showError("showCustomized error",e.toString());
240            }
241        }
242    
243        public void show(Object object) {
244            logger.debug("show("+object+")");
245            show(object,false);
246        }
247    
248        public void show(Object object,
249                         String viewType, Object[] viewParams) {
250            logger.debug("show("+object+","+viewType+","+
251                      (viewParams!=null?Arrays.asList(viewParams):null)+")");
252            show(object,viewType,viewParams,false);
253        }
254        
255        protected void show(Object object, boolean newWindow) {
256            show(object,
257                 "Object",new String[] {GuiAC.DEFAULT_VIEW},
258                 newWindow);
259        }
260    
261        protected void show(Object object,
262                            String viewType, Object[] viewParams,
263                            boolean newWindow) {
264            if (object==null) {
265                refresh();
266                /*
267                  } else if (object instanceof Throwable) {
268                  showError("Exception",object.toString());
269                  ((Throwable)object).printStackTrace();
270                */
271            } else if (object instanceof InputStream) {
272                try {
273                    byte[] buffer = new byte[4096];
274                    InputStream input = (InputStream)object;
275                    int length;
276                    OutputStream output = getResponse().getOutputStream();
277                    while ((length=input.read(buffer))!=-1) {
278                        output.write(buffer,0,length);
279                    }
280                } catch (IOException e) {
281                    logger.error("Failed to output stream",e);
282                } finally {
283                    getRequest().setResponse();
284                }
285            } else if (object instanceof Attachment) {
286                try {
287                    DisplayContext context = new DisplayContext(this,null);
288                    HttpServletResponse response = getResponse();
289                    response.setContentType(((Attachment)object).getMimeType());
290                    response.getOutputStream().write(((Attachment)object).getData());
291                } catch (IOException e) {
292                    logger.error("Failed to output stream",e);
293                } finally {
294                    getRequest().setResponse();
295                }
296            } else {
297                DisplayContext context = new DisplayContext(this,null);
298                View objectView = 
299                    factory.createView("object",
300                                       viewType,ExtArrays.add(object,viewParams),
301                                       context);
302                String title;
303                if (object!=null) {
304                    Class substance_type = object.getClass();
305                    String tn = substance_type.getName();
306                
307                    title = tn.substring( tn.lastIndexOf('.') + 1) + " " +
308                        GuiAC.toString(object);
309                } else {
310                    title= "<null>";
311                }
312    
313                View page = factory.createView(
314                    title,"Window",
315                    new Object[] {objectView,ExtBoolean.valueOf(newWindow)},
316                    context);
317                context.setWindow(page);
318                session.newRequest(new Request(page));
319                refresh();
320            }
321        }
322    
323        public void openView(Object object) {
324            logger.debug("openView("+object+")");
325            show(object,true);
326        }
327    
328        public boolean showModal(Object object, String title, String header, 
329                                 Object parent,
330                                 boolean okButton, boolean cancelButton, 
331                                 boolean closeButton)
332        {
333            logger.debug("showModal("+object+","+title+")");
334            DisplayContext context = new DisplayContext(this,null);
335            logger.debug("new context "+context);
336            View objectView = factory.createObjectView("object",object,context);
337    
338            return showModal(objectView,context,
339                             title,header,parent,
340                             okButton,cancelButton,closeButton);
341        }
342    
343        public boolean showModal(Object object, 
344                                 String viewType, Object[] viewParams,
345                                 String title, String header, 
346                                 Object parent,
347                                 boolean okButton, boolean cancelButton, 
348                                 boolean closeButton)
349        {
350            logger.debug("showModal("+object+","+title+","+viewType+")");
351            DisplayContext context = new DisplayContext(this,null);
352            View objectView = factory.createView("object",viewType,
353                                                 ExtArrays.add(object,viewParams),context);
354    
355            return showModal(objectView,context,
356                             title,header,parent,
357                             okButton,cancelButton,closeButton);
358        }
359    
360        public boolean showModal(View objectView, DisplayContext context,
361                                 String title, String header, 
362                                 Object parent,
363                                 boolean okButton, boolean cancelButton, 
364                                 boolean closeButton) 
365        {
366            logger.debug("objectView = "+objectView+", context="+Strings.hex(context));
367            if (objectView instanceof org.objectweb.jac.aspects.gui.EditorContainer)
368                ((org.objectweb.jac.aspects.gui.EditorContainer)objectView).setShowButtons(false);
369             
370            DialogView page = 
371                (DialogView)factory.createView(
372                    title,"Dialog",
373                    new Object[] {objectView,parent,title,header},
374                    context);
375            context.setWindow(page);
376            session.newRequest(new Request(page));
377            refresh();
378    
379            try {
380                boolean result = page.waitForClose();
381                WebDisplay.setResponse(((Dialog)page).getResponse());
382                WebDisplay.setRequest(((Dialog)page).getRequest());
383                return result;
384            } catch (TimeoutException timeout) {
385                addTimedoutDialog(page);
386                throw timeout;
387            }
388        }
389    
390        public boolean showInput(Object substance, AbstractMethodItem method, 
391                                 Object[] parameters) 
392        {
393            logger.debug("showInput("+substance+","+method+","+
394                      Arrays.asList(parameters)+")");
395            DisplayContext dc = (DisplayContext)Collaboration.get()
396                .getAttribute(GuiAC.DISPLAY_CONTEXT);
397            //if (dc==null) {
398            dc = new DisplayContext(this,null);
399            //}
400            DialogView page = GenericFactory.createInputDialog(substance,
401                                                               method,parameters,dc);
402            dc.setWindow(page);
403            session.newRequest(new Request(page));
404            refresh();
405            try {
406                if (page.waitForClose()) {
407                    EditorContainer inputView = (EditorContainer)page.getContentView();
408                    Iterator it = inputView.getEditors().iterator();
409                    int i=0;
410                    JacRequest request = getRequest();
411                    while (it.hasNext()) {
412                        if (method.getParameterTypes()[i] != DisplayContext.class) {
413                            FieldEditor editor = (FieldEditor)it.next();
414                            method.setParameter(parameters,i,editor.getValue());
415                        }
416                        i++;
417                    }
418                    setResponse(((Dialog)page).getResponse());
419                    setRequest(((Dialog)page).getRequest());
420                    return true;
421                } else {
422                    setResponse(((Dialog)page).getResponse());
423                    setRequest(((Dialog)page).getRequest());
424                    return false;
425                }
426            } catch (TimeoutException timeout) {
427                addTimedoutDialog(page);
428                throw timeout;
429            }
430        }
431    
432        public boolean showMessage(String message, String title,
433                                   boolean okButton, 
434                                   boolean cancelButton, 
435                                   boolean closeButton ) {
436            logger.debug("showMessage("+message+","+title+")");
437            return showModal(null,title,message,null,okButton,cancelButton,closeButton);
438        }
439    
440        protected View buildMessage(String title, String message) {
441            DisplayContext context=(DisplayContext)Collaboration.get()
442                .getAttribute(GuiAC.DISPLAY_CONTEXT);
443            if(context==null) {
444                context=new DisplayContext(this,null);
445            }
446            View label = factory.createView("message","text",
447                                            new Object[] {message,null,null},context);
448            View page = factory.createView("Object view","Window",
449                                           new Object[] {label,Boolean.FALSE},context);
450            return page;
451        }
452    
453        public void showMessage(String title, String message) {
454            logger.debug("showMessage("+title+","+message+")");
455            try {
456                View page = buildMessage(title,message);
457                session.newRequest(new Request(page));
458            } finally {
459                refresh();
460            }
461        }
462    
463        public Object showRefreshMessage(String title, String message) {
464            View page = null;
465            try {
466                DisplayContext context=(DisplayContext)Collaboration.get()
467                    .getAttribute(GuiAC.DISPLAY_CONTEXT);
468                if (context==null) {
469                    context=new DisplayContext(this,null);
470                }
471                View label = factory.createView("message","text",
472                                                new Object[] {message,null,null},context);
473                page = factory.createView("Object view","RefreshWindow",
474                                               new Object[] {label},context);
475                session.newRequest(new Request(page));
476            } finally {
477                refresh();
478            }
479            return page;
480        }
481    
482        public void showError(String title, String message) {
483            showMessage(title,message);
484        }
485    
486        public void showStatus(String message) {
487        }
488    
489        public void applicationStarted() {
490        }
491       
492    
493        public CustomizedView getCustomizedView(String customizedID) {
494            return (CustomizedView)frames.get(customizedID);
495        }
496        public Collection getCustomizedViews() {
497            return frames.values();
498        }
499        public ViewFactory getFactory() {
500            return factory;
501        }
502    
503        String displayID;
504    
505        public String getDisplayID() {
506            return displayID;
507        }
508    
509        public void setDisplayID(String displayID) {
510            this.displayID = displayID;
511        }
512    
513        public void close() {
514            // close all customized guis
515            Iterator i = frames.values().iterator();
516            while(i.hasNext()) {
517                View view = (View)i.next();
518                view.close(true);
519            }
520        }   
521    
522        // definition of AbstractServer abstract methods
523    
524        public Object[] buildParameterValues(AbstractMethodItem method, String[] params) {
525            Object[] result = new Object[params.length];
526            Class[] pts = method.getParameterTypes();
527            for(int i=0; i<params.length; i++)
528            {
529                //         HTMLEditor editor = getEditorComponent(null,method,i);
530                //         result[i] = editor.getValue(params[i]);
531            }
532            return result;
533        }
534    
535        public void refresh() {
536            if (getResponse()==null) {
537                logger.debug("refresh ignored since response==null");
538                return;
539            }
540            logger.debug("refresh");
541            Object view = session.getCurrentRequest().getView();
542            if (view == null) {
543                showError("Display error","Nothing to refresh yet");
544                logger.debug("Nothing to refresh yet");
545            } else {
546                try {
547                    loggerHtml.debug("genHTML on "+view+"("+Strings.hex(getResponse())+")");
548                    ((HTMLViewer)view).genHTML(getResponse().getWriter());
549                } catch(Exception e) {
550                    logger.error("refresh failed",e);
551                } finally {
552                    getRequest().setResponse();
553                } 
554            }      
555        }
556    
557        static HttpServer httpServer;
558        public static HttpServer getHttpServer() {
559            if (httpServer==null) {
560                httpServer = new HttpServer();
561            }
562            return httpServer;
563        } 
564    
565        /** Alreeady started GUIs */
566        static HashSet startedGuis = new HashSet();
567        /** TCP Ports the servlet engine listens to */
568        static HashSet openedPorts = new HashSet();
569    
570        /**
571         * Start JAC's internal webserver. You can then interact with the
572         * application through an URL like this: 
573         * http://<hostname>:<port>/<application>/<guiID>
574         *
575         * @param application name of the application
576         * @param guiIDs name of windows. gui_name[:port]
577         * @param defaultPort default TCP port on which to listen */
578        public static void startWebServer(String application, String[] guiIDs, int defaultPort) 
579            throws IOException, MultiException
580        {
581            loggerWeb.debug("startWebServer on port "+defaultPort);
582            httpServer = getHttpServer();
583            HttpContext context = httpServer.getContext("/jac");
584    
585            ServletHandler servletHandler = new ServletHandler();
586            for (int i=0; i<guiIDs.length;i++) {
587                if (startedGuis.contains(guiIDs[i])) {
588                    loggerWeb.debug("skipping already registered GUI "+guiIDs[i]);
589                } else {
590                    loggerWeb.debug("register GUI "+guiIDs[i]);
591    
592                    String split[] = Strings.split(guiIDs[i],":");
593                    String gui = split[0];
594                    int port = split.length>=2 ? Integer.parseInt(split[1]) : defaultPort;
595                
596                    if (!openedPorts.contains(new Integer(port))) {
597                        httpServer.addListener(new InetAddrPort(port));
598                        openedPorts.add(new Integer(port));
599                    }
600                
601                    // Make sure the servlet will have the JAC ClassLoader
602                    // or it won't get the right NameRepository
603                    context.setClassLoader(WebDisplay.class.getClassLoader());
604             
605                    servletHandler.addServlet("Jac","/"+gui,
606                                              "org.objectweb.jac.aspects.gui.web.JacLocalServlet");
607                }
608            }
609            context.addHandler(servletHandler);
610    
611            context = httpServer.getContext("/jac/resources");
612            context.setBaseResource(new ClasspathResource());
613            context.setClassLoader(WebDisplay.class.getClassLoader());
614            context.addHandler(new ResourceHandler());
615    
616            startWebServer();
617        }
618    
619        /**
620         * Start the web server if it is not already started
621         */
622        public static synchronized void startWebServer() throws MultiException {
623            if (httpServer.isStarted()) {
624                try {
625                    httpServer.stop();
626                } catch (Exception e) {
627                    e.printStackTrace();
628                }
629            } 
630            httpServer.start();
631        }
632    
633        // View -> (String)id
634        Hashtable ids = new Hashtable();
635        // (String)id -> View
636        Hashtable views = new Hashtable();
637    
638        /**
639         * Compute an id for a view and register it.
640         * @param view the view to register
641         * @return the id of the view
642         * @see #unregisterView(View)
643         * @see #getView(String)
644         */
645        public String registerView(View view) {
646            String id = (String)ids.get(view);
647            if (id==null) {
648                id = Integer.toString(view.hashCode());
649                ids.put(view,id);
650                views.put(id,view);
651                loggerViews.debug(toString()+".registerView("+view+") -> "+id);
652            }
653            return id;
654        }
655    
656        /**
657         * Unregister a view.
658         * @param view the view to register
659         * @see #registerView(View)
660         */
661        public void unregisterView(View view) { 
662            Object id = ids.get(view);
663            loggerViews.debug(toString()+".unregisterView("+view+") "+id);
664            ids.remove(view);
665            if (id!=null) {
666                views.remove(id);
667            }
668        }
669    
670        /**
671         * Returns the view registered with a given id
672         * @param id the id of the view
673         * @return the registered view with that id, null if there is none
674         * @see #registerView(View)
675         */
676        public View getView(String id) {
677            loggerViews.debug(toString()+".getView("+id+")");
678            return (View)views.get(id);
679        }
680    
681        /**
682         * Gets the ID of a registered view.
683         *
684         * @param view the view
685         * @return the ID of the view 
686         */
687        public String getViewID(View view) {
688            return (String)ids.get(view);
689        }
690    
691        // Strings
692        HashSet timedoutDialogs = new HashSet();
693        public void addTimedoutDialog(DialogView dialog) {
694            timedoutDialogs.add(getViewID(dialog));
695        }
696        /**
697         * Tells wether a view ID corresponds to a timedout dialog
698         */
699        public boolean isTimedout(String viewID) {
700            return timedoutDialogs.contains(viewID);
701        }
702    
703        String servletName;
704        /**
705         * Set the name of the servlet
706         * @param name the name of the servlet
707         */
708        public void setServletName(String name) {
709            servletName = name;
710        }
711        /**
712         * Returns the name of the servlet
713         */
714        public String getServletName() {
715            return servletName;
716        }
717    
718        public void closeWindow(View window, boolean validate) {
719            loggerWeb.debug("closeWindow "+window);
720            Iterator i = session.getRequests().iterator();
721            Request request = null;
722            while (i.hasNext()) {
723                request = (Request)i.next();
724                if (request.getView().equals(window)) {
725                    // we MUST do the close() before remove() because if
726                    // close() fails with an exception, we do not want to
727                    // remove.
728                    ((View)request.getView()).close(validate);
729                    session.getRequests().remove(request);
730                    break;
731                }
732            }
733        }
734    
735        public Session getSession() {
736            return session;
737        }
738    
739        public boolean fillParameters(AbstractMethodItem method, Object[] parameters) {
740            Class[] paramTypes = method.getParameterTypes();
741            for (int i=0; i<paramTypes.length; i++) {
742                if (Writer.class.isAssignableFrom(paramTypes[i]) 
743                    && parameters[i]==null) 
744                {
745                    String type = (String)method.getAttribute(GuiAC.MIME_TYPE);
746                    if (type!=null) {
747                        logger.info("Setting mime-type: "+type);
748                        getResponse().setContentType(type+"; "+GuiAC.getEncoding());
749                    }
750                    try {
751                        parameters[i] = 
752                            new OutputStreamWriter(
753                                getResponse().getOutputStream(),
754                                GuiAC.getEncoding());
755                        grabResponse(); // So that no one else can used it
756                    } catch(Exception e) {
757                        logger.error("Failed to set Writer parameter "+i+
758                                     " for "+method.getLongName(),e);
759                    }
760                    return paramTypes.length<=1;
761                } else if (OutputStream.class.isAssignableFrom(paramTypes[i]) 
762                           && parameters[i]==null) {
763                    String type = (String)method.getAttribute(GuiAC.MIME_TYPE);
764                    if (type!=null) {
765                        logger.info("Setting mime-type: "+type);
766                        getResponse().setContentType(type+"; "+GuiAC.getEncoding());
767                    }
768                    try {
769                        parameters[i] = getResponse().getOutputStream();
770                        grabResponse(); // So that no one else can used it
771                    } catch(Exception e) {
772                        logger.error("Failed to set OutputStream parameter "+i+
773                                     " for "+method.getLongName(),e);
774                    }
775                    return paramTypes.length<=1;
776                }
777            }
778    
779            return false;
780        }
781    
782        /**
783         * Ensure that we will send any HTML output to the currently connection.
784         */
785        public void onInvocationReturn(Object substance, AbstractMethodItem method) {
786            Class[] paramTypes = method.getParameterTypes();
787            for (int i=0; i<paramTypes.length; i++) {
788                // TODO: only call setResponse() for the invocation for
789                // which the HttpResponse was grabbed
790                if (Writer.class.isAssignableFrom(paramTypes[i]) || 
791                    OutputStream.class.isAssignableFrom(paramTypes[i]))
792                {
793                    getRequest().setResponse();
794                }
795            }
796        }
797    
798    }