1 /*************************************************************************
2  *
3  *  The Contents of this file are made available subject to the terms of
4  *  the BSD license.
5  *
6  *  Copyright 2000, 2010 Oracle and/or its affiliates.
7  *  All rights reserved.
8  *
9  *  Redistribution and use in source and binary forms, with or without
10  *  modification, are permitted provided that the following conditions
11  *  are met:
12  *  1. Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer.
14  *  2. Redistributions in binary form must reproduce the above copyright
15  *     notice, this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  *  3. Neither the name of Sun Microsystems, Inc. nor the names of its
18  *     contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission.
20  *
21  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28  *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29  *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
30  *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
31  *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  *
33  *************************************************************************/
34 
35 // __________ Imports __________
36 
37 import com.sun.star.uno.UnoRuntime;
38 
39 import java.lang.*;
40 import javax.swing.*;
41 import java.util.Vector;
42 
43 // __________ Implementation __________
44 
45 /**
46  * This class can be used to intercept dispatched URL's
47  * on any frame used in this demo application.
48  * It intercept all URL's wich try to create a new empty frame.
49  * (e.g. "private:factory/swriter")
50  * Nobody can guarantee that this interception will be realy used -
51  * because another interceptor (registered at a later time then this one!)
52  * will be called before this one.
53  * Implementation is executed inside a new thread to prevent application
54  * against possible deadlocks. This deadlocks can occure if
55  * synchronous/asynchronous ... normal ones and oneway calls are mixed.
56  * Notifications of listener will be oneway mostly - her reactions can
57  * be synchronous then. => deadlocks are possible
58  *
59  * @author     Andreas Schlüns
60  * @created    06.03.2002 09:38
61  */
62 public class Interceptor implements com.sun.star.frame.XFrameActionListener,
63                                     com.sun.star.frame.XDispatchProviderInterceptor,
64                                     com.sun.star.frame.XDispatchProvider,
65                                     com.sun.star.frame.XDispatch,
66                                     com.sun.star.frame.XInterceptorInfo,
67                                     IShutdownListener,
68                                     IOnewayLink
69 {
70     // ____________________
71 
72     /**
73      * const
74      * All these URL's are intercepted by this implementation.
75      */
76     private static final String[] INTERCEPTED_URLS = { "private:factory/*" ,
77                                                        ".uno:SaveAs"       ,
78                                                        "slot:5300"         ,
79                                                        ".uno:Quit"         };
80 
81     // ____________________
82 
83     /**
84      * @member m_xMaster     use this interceptor if he doesn't handle queried dispatch request
85      * @member m_xSlave      we can forward all unhandled requests to this slave interceptor
86      * @member m_xFrame      intercepted frame
87      * @member m_bDead       there exist more then one way to finish an object of this class - we must know it sometimes
88      */
89     private com.sun.star.frame.XDispatchProvider m_xMaster            ;
90     private com.sun.star.frame.XDispatchProvider m_xSlave             ;
91     private com.sun.star.frame.XFrame            m_xFrame             ;
92     private boolean                              m_bIsActionListener  ;
93     private boolean                              m_bIsRegistered      ;
94     private boolean                              m_bDead              ;
95 
96     // ____________________
97 
98     /**
99      * ctor
100      * Initialize the new interceptor. Given frame reference can be used to
101      * register this interceptor on it automaticly later.
102      *
103      * @seealso startListening()
104      *
105      * @param xFrame
106      *          this interceptor will register himself at this frame to intercept dispatched URLs
107      */
108     Interceptor(/*IN*/ com.sun.star.frame.XFrame xFrame)
109     {
110         m_xFrame            = xFrame ;
111         m_xSlave            = null   ;
112         m_xMaster           = null   ;
113         m_bIsRegistered     = false  ;
114         m_bIsActionListener = false  ;
115         m_bDead             = false  ;
116     }
117 
118     //_____________________
119 
120     /**
121      * start working as frame action listener realy.
122      * We will be frame action listener here. In case
123      * we get a frame action which indicates, that we should
124      * update our interception. Because such using of an interecptor
125      * isn't guaranteed - in case a newer one was registered ...
126      */
127     public void startListening()
128     {
129         com.sun.star.frame.XFrame xFrame = null;
130         synchronized(this)
131         {
132             if (m_bDead)
133                 return;
134             if (m_xFrame==null)
135                 return;
136             if (m_bIsActionListener==true)
137                 return;
138             xFrame = m_xFrame;
139         }
140         m_xFrame.addFrameActionListener(this);
141         synchronized(this)
142         {
143             m_bIsActionListener=true;
144         }
145     }
146 
147     //_____________________
148 
149     /**
150      * In case we got an oneway listener callback - we had to use the office
151      * asynchronous then. This method is the callback from the started thread
152      * (started inside the original oneway method). We found all parameters of
153      * the original request packed inside a vector. Here we unpack it and
154      * call the right internal helper method, which implements the right
155      * funtionality.
156      *
157      * @seealso frameAction()
158      * @seealso dispatch()
159      *
160      * @param nRequest
161      *          indicates, which was the original request (identifies the
162      *          original called method)
163      *
164      * @param lParams
165      *          the vector with all packed parameters of the original request
166      */
167     public void execOneway(/*IN*/ int nRequest,/*IN*/ Vector lParams )
168     {
169         synchronized(this)
170         {
171             if (m_bDead)
172                 return;
173         }
174 
175         // was it frameAction()?
176         if (nRequest==OnewayExecutor.REQUEST_FRAMEACTION)
177         {
178             com.sun.star.frame.FrameActionEvent[] lOutAction   = new com.sun.star.frame.FrameActionEvent[1];
179             Vector[]                              lInParams    = new Vector[1];
180                                                   lInParams[0] = lParams;
181 
182             OnewayExecutor.codeFrameAction( OnewayExecutor.DECODE_PARAMS ,
183                                             lInParams                    ,
184                                             lOutAction                   );
185             impl_frameAction(lOutAction[0]);
186         }
187         else
188         // was it dispatch()?
189         if (nRequest==OnewayExecutor.REQUEST_DISPATCH)
190         {
191             com.sun.star.util.URL[]              lOutURL      = new com.sun.star.util.URL[1];
192             com.sun.star.beans.PropertyValue[][] lOutProps    = new com.sun.star.beans.PropertyValue[1][];
193             Vector[]                             lInParams    = new Vector[1];
194                                                  lInParams[0] = lParams;
195 
196             OnewayExecutor.codeDispatch( OnewayExecutor.DECODE_PARAMS ,
197                                          lInParams                    ,
198                                          lOutURL                      ,
199                                          lOutProps                    );
200             impl_dispatch(lOutURL[0],lOutProps[0]);
201         }
202     }
203 
204     // ____________________
205 
206     /**
207      * call back for frame action events
208      * We use it to update our interception. Because if a new component was loaded into
209      * the frame or another interceptor was registered, we should refresh our connection
210      * to the frame. Otherwhise we can't guarantee full functionality here.
211      *
212      * Note: Don't react synchronous in an asynchronous listener callback. So use a thread
213      * here to update anything.
214      *
215      * @seealso impl_frameAction()
216      *
217      * @param aEvent
218      *          describes the action
219      */
220     public /*ONEWAY*/  void frameAction(/*IN*/ com.sun.star.frame.FrameActionEvent aEvent)
221     {
222         synchronized(this)
223         {
224             if (m_bDead)
225                 return;
226         }
227 
228         boolean bHandle = false;
229         switch(aEvent.Action.getValue())
230         {
231             case com.sun.star.frame.FrameAction.COMPONENT_ATTACHED_value   : bHandle=true; break;
232             case com.sun.star.frame.FrameAction.COMPONENT_DETACHING_value  : bHandle=true; break;
233             case com.sun.star.frame.FrameAction.COMPONENT_REATTACHED_value : bHandle=true; break;
234             // Don't react for CONTEXT_CHANGED here. Ok it indicates, that may another interceptor
235             // was registered at the frame ... but if we register ourself there - we get a context
236             // changed too :-( Best way to produce a never ending recursion ...
237             // May be that somewhere find a safe mechanism to detect own produced frame action events
238             // and ignore it.
239             case com.sun.star.frame.FrameAction.CONTEXT_CHANGED_value :
240                     System.out.println("Time to update interception ... but may it will start a recursion. So I let it :-(");
241                     bHandle=false;
242                     break;
243         }
244 
245         // ignore some events
246         if (! bHandle)
247             return;
248 
249         // pack the event and start thread - which call us back later
250         Vector[]                              lOutParams   = new Vector[1];
251         com.sun.star.frame.FrameActionEvent[] lInAction    = new com.sun.star.frame.FrameActionEvent[1];
252                                               lInAction[0] = aEvent;
253 
254         OnewayExecutor.codeFrameAction( OnewayExecutor.ENCODE_PARAMS ,
255                                         lOutParams                   ,
256                                         lInAction                    );
257         OnewayExecutor aExecutor = new OnewayExecutor( (IOnewayLink)this                  ,
258                                                        OnewayExecutor.REQUEST_FRAMEACTION ,
259                                                        lOutParams[0]                      );
260         aExecutor.start();
261     }
262 
263     // ____________________
264 
265     /**
266      * Indicates using of us as an interceptor.
267      * Now we have to react for the requests, we are registered.
268      * That means: load new empty documents - triggered by the new menu of the office.
269      * Because it's oneway - use thread for loading!
270      *
271      * @seealso impl_dispatch()
272      *
273      * @param aURL
274      *          describes the document, which should be loaded
275      *
276      * @param lArguments
277      *          optional parameters for loading
278      */
279     public /*ONEWAY*/ void dispatch(/*IN*/ com.sun.star.util.URL aURL,/*IN*/ com.sun.star.beans.PropertyValue[] lArguments)
280     {
281         synchronized(this)
282         {
283             if (m_bDead)
284                 return;
285         }
286 
287         Vector[]                             lOutParams      = new Vector[1];
288         com.sun.star.util.URL[]              lInURL          = new com.sun.star.util.URL[1];
289         com.sun.star.beans.PropertyValue[][] lInArguments    = new com.sun.star.beans.PropertyValue[1][];
290                                              lInURL[0]       = aURL      ;
291                                              lInArguments[0] = lArguments;
292 
293         OnewayExecutor.codeDispatch( OnewayExecutor.ENCODE_PARAMS ,
294                                      lOutParams                   ,
295                                      lInURL                       ,
296                                      lInArguments                 );
297         OnewayExecutor aExecutor = new OnewayExecutor( (IOnewayLink)this               ,
298                                                        OnewayExecutor.REQUEST_DISPATCH ,
299                                                        lOutParams[0]                   );
300         aExecutor.start();
301     }
302 
303 
304     //_____________________
305 
306     /**
307      * Internal call back for frame action events, triggered by the used
308      * OnewayExecutor thread we started in frameAction().
309      * We use it to update our interception on the internal saved frame.
310      *
311      * @param aEvent
312      *          describes the action
313      */
314     public void impl_frameAction(/*IN*/ com.sun.star.frame.FrameActionEvent aEvent)
315     {
316         synchronized(this)
317         {
318             if (m_bDead)
319                 return;
320         }
321 
322         // deregistration will be done everytime ...
323         // But may it's not neccessary to establish a new registration!
324         // Don't look for ignoring actions - it was done already inside original frameAction() call!
325         boolean bRegister = false;
326 
327         // analyze the event and decide which reaction is usefull
328         switch(aEvent.Action.getValue())
329         {
330             case com.sun.star.frame.FrameAction.COMPONENT_ATTACHED_value   : bRegister = true ; break;
331             case com.sun.star.frame.FrameAction.COMPONENT_REATTACHED_value : bRegister = true ; break;
332             case com.sun.star.frame.FrameAction.COMPONENT_DETACHING_value  : bRegister = false; break;
333         }
334 
335         com.sun.star.frame.XFrame xFrame        = null ;
336         boolean                   bIsRegistered = false;
337         synchronized(this)
338         {
339             bIsRegistered   = m_bIsRegistered;
340             m_bIsRegistered = false;
341             xFrame          = m_xFrame;
342         }
343 
344         com.sun.star.frame.XDispatchProviderInterception xRegistration = (com.sun.star.frame.XDispatchProviderInterception)UnoRuntime.queryInterface(
345             com.sun.star.frame.XDispatchProviderInterception.class,
346             xFrame);
347 
348         if(xRegistration==null)
349             return;
350 
351         if (bIsRegistered)
352             xRegistration.releaseDispatchProviderInterceptor(this);
353 
354         if (! bRegister)
355             return;
356 
357         xRegistration.registerDispatchProviderInterceptor(this);
358         synchronized(this)
359         {
360             m_bIsRegistered = true;
361         }
362     }
363 
364     // ____________________
365 
366     /**
367      * Implementation of interface XDispatchProviderInterceptor
368      * These functions are used to build a list of interceptor objects
369      * connected in both ways.
370      * Searching for a right interceptor is made by forwarding any request
371      * from toppest master to lowest slave of this hierarchy.
372      * If an interceptor whish to handle the request he can break that
373      * and return himself as a dispatcher.
374      */
375     public com.sun.star.frame.XDispatchProvider getSlaveDispatchProvider()
376     {
377         synchronized(this)
378         {
379             return m_xSlave;
380         }
381     }
382 
383     // ____________________
384 
385     public void setSlaveDispatchProvider(com.sun.star.frame.XDispatchProvider xSlave)
386     {
387         synchronized(this)
388         {
389             m_xSlave = xSlave;
390         }
391     }
392 
393     // ____________________
394 
395     public com.sun.star.frame.XDispatchProvider getMasterDispatchProvider()
396     {
397         synchronized(this)
398         {
399             return m_xMaster;
400         }
401     }
402 
403     // ____________________
404 
405     public void setMasterDispatchProvider(com.sun.star.frame.XDispatchProvider xMaster)
406     {
407         synchronized(this)
408         {
409             m_xMaster = xMaster;
410         }
411     }
412 
413     // ____________________
414 
415     /**
416      * Implementation of interface XDispatchProvider
417      * These functions are called from our master if he willn't handle the outstanding request.
418      * Given parameter should be checked if they are right for us. If it's true, the returned
419      * dispatcher should be this implementation himself; otherwise call should be forwarded
420      * to the slave.
421      *
422      * @param aURL
423      *          describes the request, which should be handled
424      *
425      * @param sTarget
426      *          specifies the target frame for this request
427      *
428      * @param nSearchFlags
429      *          optional search flags, if sTarget isn't a special one
430      *
431      * @return [XDispatch]
432      *          a dispatch object, which can handle the given URL
433      *          May be NULL!
434      */
435     public com.sun.star.frame.XDispatch queryDispatch(/*IN*/ com.sun.star.util.URL aURL,/*IN*/ String sTarget,/*IN*/ int nSearchFlags)
436     {
437         synchronized(this)
438         {
439             if (m_bDead)
440                 return null;
441         }
442 
443         // intercept loading empty documents into new created frames
444         if(
445             (sTarget.compareTo       ("_blank"         ) == 0   ) &&
446             (aURL.Complete.startsWith("private:factory") == true)
447           )
448         {
449             System.out.println("intercept private:factory");
450             return this;
451         }
452 
453         // intercept opening the SaveAs dialog
454         if (aURL.Complete.startsWith(".uno:SaveAs") == true)
455         {
456             System.out.println("intercept SaveAs by returning null!");
457             return null;
458         }
459 
460         // intercept "File->Exit" inside the menu
461         if (
462             (aURL.Complete.startsWith("slot:5300") == true)  ||
463             (aURL.Complete.startsWith(".uno:Quit") == true)
464            )
465         {
466             System.out.println("intercept File->Exit");
467             return this;
468         }
469 
470         synchronized(this)
471         {
472             if (m_xSlave!=null)
473                 return m_xSlave.queryDispatch(aURL, sTarget, nSearchFlags);
474         }
475 
476         return null;
477     }
478 
479     // ____________________
480 
481     public com.sun.star.frame.XDispatch[] queryDispatches(/*IN*/ com.sun.star.frame.DispatchDescriptor[] lDescriptor)
482     {
483         synchronized(this)
484         {
485             if (m_bDead)
486                 return null;
487         }
488         // Resolve any request seperatly by using own "dispatch()" method.
489         // Note: Don't pack return list if "null" objects occure!
490         int                            nCount      = lDescriptor.length;
491         com.sun.star.frame.XDispatch[] lDispatcher = new com.sun.star.frame.XDispatch[nCount];
492         for(int i=0; i<nCount; ++i)
493         {
494             lDispatcher[i] = queryDispatch(lDescriptor[i].FeatureURL ,
495                                            lDescriptor[i].FrameName  ,
496                                            lDescriptor[i].SearchFlags);
497         }
498         return lDispatcher;
499     }
500 
501     // ____________________
502 
503     /**
504      * This method is called if this interceptor "wins the request".
505      * We intercepted creation of new frames and loading of empty documents.
506      * Do it now.
507      *
508      * @param aURL
509      *          describes the document
510      *
511      * @param lArguments
512      *          optional arguments for loading
513      */
514     public void impl_dispatch(/*IN*/ com.sun.star.util.URL aURL,/*IN*/ com.sun.star.beans.PropertyValue[] lArguments)
515     {
516         synchronized(this)
517         {
518             if (m_bDead)
519                 return;
520         }
521 
522         if (
523             (aURL.Complete.startsWith("slot:5300") == true) ||
524             (aURL.Complete.startsWith(".uno:Quit") == true)
525            )
526         {
527             System.exit(0);
528         }
529         else
530         if (aURL.Complete.startsWith("private:factory") == true)
531         {
532             // Create view frame for showing loaded documents on demand.
533             // The visible state is neccessary for JNI functionality to get the HWND and plug office
534             // inside a java window hierarchy!
535             DocumentView aNewView = new DocumentView();
536             aNewView.setVisible(true);
537             aNewView.createFrame();
538             aNewView.load(aURL.Complete,lArguments);
539         }
540     }
541 
542     // ____________________
543 
544     /**
545      * Notification of status listener isn't guaranteed (instead of listener on XNotifyingDispatch interface).
546      * So this interceptor doesn't support that realy ...
547      */
548     public /*ONEWAY*/ void addStatusListener(/*IN*/ com.sun.star.frame.XStatusListener xListener,/*IN*/ com.sun.star.util.URL aURL)
549     {
550 /*        if (aURL.Complete.startsWith(".uno:SaveAs")==true)
551         {
552             com.sun.star.frame.FeatureStateEvent aEvent = new com.sun.star.frame.FeatureStateEvent(
553                                                                 this,
554                                                                 aURL,
555                                                                 "",
556                                                                 false,
557                                                                 false,
558                                                                 null);
559             if (xListener!=null)
560             {
561                 System.out.println("interceptor disable SavAs by listener notify");
562                 xListener.statusChanged(aEvent);
563             }
564         }*/
565     }
566 
567     // ____________________
568 
569     public /*ONEWAY*/ void removeStatusListener(/*IN*/ com.sun.star.frame.XStatusListener xListener,/*IN*/ com.sun.star.util.URL aURL)
570     {
571     }
572 
573     // ____________________
574 
575     /**
576      * Implements (optional!) optimization for interceptor mechanism.
577      * Any interceptor which provides this special interface is called automaticly
578      * at registration time on this method. Returned URL's will be used to
579      * call this interceptor directly without calling his masters before, IF(!)
580      * following rules will be true:
581      *      (1) every master supports this optional interface too
582      *      (2) nobody of these masters whish to intercept same URL then this one
583      * This interceptor whish to intercept creation of new documents.
584      */
585     public String[] getInterceptedURLs()
586     {
587         return INTERCEPTED_URLS;
588     }
589 
590     // ____________________
591 
592     /**
593      * This class listen on the intercepted frame to free all used ressources on closing.
594      * We forget the reference to the frame only here. Deregistration
595      * isn't neccessary here - because this frame dies and wish to forgoten.
596      *
597      * @param aSource
598      *          must be our internal saved frame, on which we listen for frame action events
599      */
600     public /*ONEAY*/ void disposing(/*IN*/ com.sun.star.lang.EventObject aSource)
601     {
602         synchronized(this)
603         {
604             if (m_bDead)
605                 return;
606             if (m_xFrame!=null && UnoRuntime.areSame(aSource.Source,m_xFrame))
607             {
608                 m_bIsActionListener = false;
609                 m_xFrame            = null ;
610             }
611         }
612         shutdown();
613     }
614 
615     // ____________________
616 
617     /**
618      * If this java application shutdown - we must cancel all current existing
619      * listener connections. Otherwhise the office will run into some
620      * DisposedExceptions if it tries to use these forgotten listener references.
621      * And of course it can die doing that.
622      * We are registered at a central object to be informed if the VM will exit.
623      * So we can react.
624      */
625     public void shutdown()
626     {
627         com.sun.star.frame.XFrame xFrame            = null ;
628         boolean                   bIsRegistered     = false;
629         boolean                   bIsActionListener = false;
630         synchronized(this)
631         {
632             // don't react a second time here!
633             if (m_bDead)
634                 return;
635             m_bDead = true;
636 
637             bIsRegistered       = m_bIsRegistered;
638             m_bIsRegistered     = false;
639 
640             bIsActionListener   = m_bIsActionListener;
641             m_bIsActionListener = false;
642 
643             xFrame              = m_xFrame;
644             m_xFrame            = null;
645         }
646 
647         // it's a good idead to cancel listening for frame action events
648         // before(!) we deregister us as an interceptor.
649         // Because registration and deregistratio nof interceptor objects
650         // will force sending of frame action events ...!
651         if (bIsActionListener)
652             xFrame.removeFrameActionListener(this);
653 
654         if (bIsRegistered)
655         {
656             com.sun.star.frame.XDispatchProviderInterception xRegistration = (com.sun.star.frame.XDispatchProviderInterception)UnoRuntime.queryInterface(
657                 com.sun.star.frame.XDispatchProviderInterception.class,
658                 xFrame);
659 
660             if(xRegistration!=null)
661                 xRegistration.releaseDispatchProviderInterceptor(this);
662         }
663 
664         xFrame = null;
665 
666         synchronized(this)
667         {
668             m_xMaster = null;
669             m_xSlave  = null;
670         }
671     }
672 }
673