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