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 package complex.framework.autosave;
25 
26 
27 
28 import com.sun.star.beans.PropertyValue;
29 import com.sun.star.frame.FeatureStateEvent;
30 import com.sun.star.frame.XDispatch;
31 import com.sun.star.frame.XDispatchProvider;
32 import com.sun.star.frame.XModel;
33 import com.sun.star.frame.XStatusListener;
34 import com.sun.star.frame.XStorable;
35 import com.sun.star.lang.XMultiServiceFactory;
36 import com.sun.star.sheet.FillDirection;
37 import com.sun.star.sheet.XCellSeries;
38 import com.sun.star.table.XCellRange;
39 import com.sun.star.util.XCloseable;
40 import com.sun.star.sheet.XSpreadsheet;
41 import com.sun.star.sheet.XSpreadsheetDocument;
42 import com.sun.star.sheet.XSpreadsheets;
43 import com.sun.star.uno.AnyConverter;
44 import com.sun.star.uno.Type;
45 import com.sun.star.uno.UnoRuntime;
46 import com.sun.star.uno.XInterface;
47 import com.sun.star.util.URL;
48 import com.sun.star.util.XURLTransformer;
49 import java.util.*;
50 import util.utils;
51 
52 
53 // ---------- junit imports -----------------
54 import org.junit.After;
55 import org.junit.AfterClass;
56 import org.junit.Before;
57 import org.junit.BeforeClass;
58 import org.junit.Test;
59 import org.openoffice.test.OfficeConnection;
60 import util.SOfficeFactory;
61 import static org.junit.Assert.*;
62 // ------------------------------------------
63 
64 //-----------------------------------------------
65 /** @short  Check some use cases of the AutoSave feature
66  */
67 public class AutoSave
68 {
69     //-------------------------------------------
70     class AutoSaveListener implements XStatusListener
71     {
72         private XDispatch m_xAutoSave;
73         private URL m_aRegistration;
74         private Protocol m_aLog;
75 
AutoSaveListener(XMultiServiceFactory xSMGR , XDispatch xAutoSave, Protocol aLog )76         public AutoSaveListener(XMultiServiceFactory xSMGR    ,
77                                 XDispatch            xAutoSave,
78                                 Protocol             aLog     )
79         {
80             m_aLog = aLog;
81             m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "create listener for AutoSave notifications ...");
82 
83             try
84             {
85                 m_xAutoSave = xAutoSave;
86 
87                 XURLTransformer xParser = UnoRuntime.queryInterface(XURLTransformer.class, xSMGR.createInstance("com.sun.star.util.URLTransformer"));
88                 URL[] aURL = new URL[1];
89                 aURL[0] = new URL();
90                 aURL[0].Complete = "vnd.sun.star.autorecovery:/doAutoSave";
91                 xParser.parseStrict(aURL);
92                 m_aRegistration = aURL[0];
93 
94                 m_xAutoSave.addStatusListener(this, m_aRegistration);
95                 m_aLog.log(Protocol.TYPE_INFO, "successfully registered as AutoSave listener.");
96             }
97             catch(Throwable ex)
98             {
99                 m_aLog.log(ex);
100             }
101 
102             m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "");
103         }
104 
disableListener()105         public void disableListener()
106         {
107             m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "stop listening for AutoSave notifications ...");
108 
109             XDispatch xAutoSave = null;
110             URL       aRegURL   = null;
111             synchronized (this)
112             {
113                 xAutoSave = m_xAutoSave;
114                 aRegURL   = m_aRegistration;
115             }
116 
117             try
118             {
119                 if (
120                     (xAutoSave != null) &&
121                     (aRegURL   != null)
122                    )
123                     xAutoSave.removeStatusListener(this, aRegURL);
124             }
125             catch(Throwable ex)
126             {
127                 m_aLog.log(ex);
128             }
129 
130             m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "");
131         }
132 
statusChanged(FeatureStateEvent aEvent)133         public void statusChanged(FeatureStateEvent aEvent)
134         {
135             m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "statusChanged() called from AutoSave ...");
136 
137             m_aLog.log("FeatureURL        = \""+aEvent.FeatureURL.Complete+"\"" );
138             m_aLog.log("FeatureDescriptor = \""+aEvent.FeatureDescriptor+"\""   );
139             m_aLog.log("IsEnabled         = \""+aEvent.IsEnabled+"\""           );
140             m_aLog.log("Requery           = \""+aEvent.Requery+"\""             );
141             m_aLog.log("State:"                                                 );
142             m_aLog.log(aEvent.State                                             );
143 
144             m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "");
145         }
146 
disposing(com.sun.star.lang.EventObject aEvent)147         public void disposing(com.sun.star.lang.EventObject aEvent)
148         {
149             m_aLog.log(Protocol.TYPE_INFO, "disposing() called from AutoSave.");
150             synchronized(this)
151             {
152                 m_xAutoSave     = null;
153                 m_aRegistration = null;
154             }
155         }
156     }
157 
158     //-------------------------------------------
159     // some const
160 
161     //-------------------------------------------
162     // member
163 
164     private Protocol m_aLog;
165 
166     /** points to the global uno service manager. */
167     private XMultiServiceFactory m_xSMGR = null;
168 
169     private SOfficeFactory m_aSOF;
170 
171     /** can be used to trigger/enable/disable the AutoSave feature. */
172     private XDispatch m_xAutoSave = null;
173 
174     /** a test document, which needs some time for saving to simulate concurrent
175      *  save operations. */
176     private XStorable m_xTestDoc = null;
177 
178     private XURLTransformer m_xURLParser = null;
179 
180     //-------------------------------------------
181     // test environment
182 
183     //-------------------------------------------
184     /** @short  A function to tell the framework,
185                 which test functions are available.
186 
187         @return All test methods.
188         @todo   Think about selection of tests from outside ...
189      */
190 //    public String[] getTestMethodNames()
191 //    {
192 //        return new String[]
193 //        {
194 //            "checkConcurrentAutoSaveToNormalUISave",
195 //        };
196 //    }
197 
198     //-------------------------------------------
199     /** @short  Create the environment for following tests.
200 
201         @descr  create an empty test frame, where we can load
202                 different components inside.
203      */
before()204     @Before public void before()
205     {
206         m_aLog = new Protocol(Protocol.MODE_HTML | Protocol.MODE_STDOUT, Protocol.FILTER_NONE, utils.getUsersTempDir() + "/complex_log_ascii_01.html");
207 
208         try
209         {
210             // get uno service manager from global test environment
211             m_xSMGR = getMSF();
212 
213             // get another helper to e.g. create test documents
214             m_aSOF = SOfficeFactory.getFactory(m_xSMGR);
215 
216             // create AutoSave instance
217             m_xAutoSave = UnoRuntime.queryInterface(XDispatch.class, m_xSMGR.createInstance("com.sun.star.comp.framework.AutoRecovery"));
218 
219             // prepare AutoSave
220             // make sure it will be started every 1 min
221             ConfigHelper aConfig = new ConfigHelper(m_xSMGR, "org.openoffice.Office.Recovery", false);
222             aConfig.writeRelativeKey("AutoSave", "Enabled"      , Boolean.TRUE  );
223             aConfig.writeRelativeKey("AutoSave", "TimeIntervall", new Integer(1)); // 1 min
224             aConfig.flush();
225             aConfig = null;
226 
227             // is needed to parse dispatch commands
228             m_xURLParser = UnoRuntime.queryInterface(XURLTransformer.class, m_xSMGR.createInstance("com.sun.star.util.URLTransformer"));
229 
230         }
231         catch(java.lang.Throwable ex)
232         {
233             m_aLog.log(ex);
234             fail("Couldn't create test environment");
235         }
236     }
237 
238     //-------------------------------------------
239     /** @short  close the environment.
240      */
after()241     @After public void after()
242     {
243         // ???
244     }
245 
246     //-------------------------------------------
247     // create a calc document with content, which needs some time for saving
createBigCalcDoc()248     private XInterface createBigCalcDoc()
249     {
250         m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "createBigCalcDoc() started ...");
251         try
252         {
253             m_aLog.log("Create empty calc document for testing.");
254             XSpreadsheetDocument xSheetDoc   = m_aSOF.createCalcDoc("_default");
255             m_aLog.log("Retrieve first sheet from calc document.");
256             XSpreadsheets        xSheets     = xSheetDoc.getSheets();
257             XSpreadsheet         xSheet      = (XSpreadsheet)AnyConverter.toObject(
258                                                  new Type(XSpreadsheet.class),
259                                                  xSheets.getByName(
260                                                          xSheets.getElementNames()[0]));
261             m_aLog.log("Fill two cells with value and formula.");
262             xSheet.getCellByPosition(0, 0).setValue(1);
263             xSheet.getCellByPosition(0, 1).setFormula("=a1+1");
264             m_aLog.log("Retrieve big range.");
265             XCellRange           xRange      = xSheet.getCellRangeByName("A1:Z9999");
266             XCellSeries          xSeries     = UnoRuntime.queryInterface(XCellSeries.class, xRange);
267             m_aLog.log("Duplicate cells from top to bottom inside range.");
268             xSeries.fillAuto(FillDirection.TO_BOTTOM, 2);
269             m_aLog.log("Duplicate cells from left to right inside range.");
270             xSeries.fillAuto(FillDirection.TO_RIGHT , 1);
271 
272             m_aLog.log(Protocol.TYPE_SCOPE_CLOSE | Protocol.TYPE_OK, "createBigCalcDoc() finished.");
273             return xSheetDoc;
274         }
275         catch(Throwable ex)
276         {
277             m_aLog.log(ex);
278         }
279 
280         m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "createBigCalcDoc() finished.");
281         return null;
282     }
283 
284     //-------------------------------------------
saveDoc(XInterface xDoc, String sURL)285     private void saveDoc(XInterface xDoc,
286                          String     sURL)
287     {
288         m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "saveDoc('"+sURL+"') started ...");
289         try
290         {
291             URL[] aURL       = new URL[1];
292             aURL[0]          = new URL();
293             aURL[0].Complete = ".uno:SaveAs";
294             m_xURLParser.parseStrict(aURL);
295 
296             XModel xModel = UnoRuntime.queryInterface(XModel.class, xDoc);
297             XDispatchProvider xProvider = UnoRuntime.queryInterface(XDispatchProvider.class, xModel.getCurrentController());
298             XDispatch xDispatch = xProvider.queryDispatch(aURL[0], "_self", 0);
299 
300             PropertyValue[] lArgs = new PropertyValue[3];
301             lArgs[0] = new PropertyValue();
302             lArgs[0].Name  = "URL";
303             lArgs[0].Value = sURL;
304             lArgs[1] = new PropertyValue();
305             lArgs[1].Name  = "Overwrite";
306             lArgs[1].Value = Boolean.TRUE;
307             lArgs[2] = new PropertyValue();
308             lArgs[2].Name  = "StoreTo";
309             lArgs[2].Value = Boolean.TRUE;
310 
311             xDispatch.dispatch(aURL[0], lArgs);
312 
313             m_aLog.log(Protocol.TYPE_OK, "saveDoc('"+sURL+"') = OK.");
314         }
315 /*
316         catch(com.sun.star.io.IOException exIO)
317         {
318             m_aLog.log(Protocol.TYPE_WARNING     , "got IOException on calling doc.store()."                                                            );
319             m_aLog.log(Protocol.TYPE_WARNING_INFO, "Please check the reason for that more in detail."                                                   );
320             m_aLog.log(Protocol.TYPE_WARNING_INFO, "A message like \"Concurrent save requests are not allowed.\" was intended and doesnt show an error!");
321             m_aLog.log(Protocol.TYPE_WARNING_INFO, "Message of the original exception:"                                                                 );
322             m_aLog.log(Protocol.TYPE_WARNING_INFO, exIO.getMessage());
323         }
324 */
325         catch(Throwable ex)
326         {
327             m_aLog.log(ex);
328         }
329         m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "saveDoc('"+sURL+"') finished.");
330     }
331 
332     //-------------------------------------------
closeDoc(XInterface xDoc)333     private void closeDoc(XInterface xDoc)
334     {
335         m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "closeDoc() started ...");
336 
337         try
338         {
339             Random aRandom = new Random();
340             int    nRetry  = 5;
341             while(nRetry>0)
342             {
343                 try
344                 {
345                     XCloseable xClose = UnoRuntime.queryInterface(XCloseable.class, xDoc);
346                     if (xClose != null)
347                     {
348                         xClose.close(false);
349                         m_aLog.log(Protocol.TYPE_OK, "closeDoc() = OK.");
350                         nRetry = 0;
351                     }
352                     else
353                     {
354                         m_aLog.log(Protocol.TYPE_ERROR, "closeDoc() = ERROR. Doc doesnt provide needed interface!");
355                     }
356                 }
357                 catch(com.sun.star.util.CloseVetoException exVeto)
358                 {
359                     m_aLog.log(Protocol.TYPE_WARNING     , "got CloseVetoException on calling doc.close()."                                    );
360                     m_aLog.log(Protocol.TYPE_WARNING_INFO, "Please check the reason for that more in detail."                                  );
361                     m_aLog.log(Protocol.TYPE_WARNING_INFO, "A message like \"Cant close while saving.\" was intended and doesnt show an error!");
362                     m_aLog.log(Protocol.TYPE_WARNING_INFO, exVeto.getMessage());
363                 }
364 
365                 if (nRetry > 0)
366                 {
367                     --nRetry;
368                     long nWait = (long)aRandom.nextInt(30000); // 30 sec.
369                     try
370                     {
371                         m_aLog.log(Protocol.TYPE_INFO, "sleep for "+nWait+" ms");
372                         synchronized(this)
373                         {
374                             wait(nWait);
375                         }
376                     }
377                     catch(Throwable ex)
378                     {
379                         m_aLog.log(Protocol.TYPE_WARNING     , "got exception for wait() !?");
380                         m_aLog.log(Protocol.TYPE_WARNING_INFO, ex.getMessage());
381                     }
382                 }
383             }
384         }
385         catch(Throwable ex)
386         {
387             m_aLog.log(ex);
388         }
389 
390         m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "closeDoc() finished.");
391     }
392 
393     class DocThread extends Thread
394     {
DocThread()395         DocThread()
396         {}
397 
run()398         public void run()
399         {
400             impl_checkConcurrentAutoSaveToNormalUISave();
401         }
402     }
403 
404     //-------------------------------------------
405     /** @short  check concurrent save requests to the same document
406      *          at the same time.
407      *
408      *  @descr  First we simulate an UI save by dispatching the right URL
409      *          to the document and at the same time we try to trigger an AutoSave
410      *          from another thread. So these operations should be started at the same time.
411      *          It should not crash. The AutoSave request must be postphoned.
412      */
checkConcurrentAutoSaveToNormalUISave()413     @Test public void checkConcurrentAutoSaveToNormalUISave()
414     {
415         m_aLog.log(Protocol.TYPE_TESTMARK , "AutoSave");
416         m_aLog.log(Protocol.TYPE_SCOPE_OPEN, "checkConcurrentAutoSaveToNormalUISave()");
417 
418         AutoSaveListener xListener = new AutoSaveListener(m_xSMGR, m_xAutoSave, m_aLog);
419 
420         try
421         {
422             DocThread aThread = new DocThread();
423             aThread.start();
424             aThread.join();
425         }
426         catch(Throwable ex)
427         {}
428 
429         xListener.disableListener();
430 
431         m_aLog.log(Protocol.TYPE_SCOPE_CLOSE, "checkConcurrentAutoSaveToNormalUISave()");
432         m_aLog.logStatistics();
433     }
434 
impl_checkConcurrentAutoSaveToNormalUISave()435     public void impl_checkConcurrentAutoSaveToNormalUISave()
436     {
437         Random aRandom = new Random();
438 
439         int i = 0;
440         int c = 5;
441         for (i=0; i<c; ++i)
442         {
443             XInterface xDoc = createBigCalcDoc();
444             try
445             {
446                 long nWait = (long)aRandom.nextInt(120000);
447                 m_aLog.log(Protocol.TYPE_INFO, "sleep for "+nWait+" ms");
448                 synchronized(this)
449                 {
450                     wait(nWait);
451                 }
452             }
453             catch(Throwable ex)
454             {
455                 m_aLog.log(Protocol.TYPE_WARNING     , "got exception for wait() !?");
456                 m_aLog.log(Protocol.TYPE_WARNING_INFO, ex.getMessage());
457             }
458             saveDoc(xDoc, utils.getOfficeTemp(m_xSMGR) + "/test_calc.ods");
459             closeDoc(xDoc);
460         }
461     }
462 
getMSF()463     private XMultiServiceFactory getMSF()
464     {
465         final XMultiServiceFactory xMSF1 = UnoRuntime.queryInterface(XMultiServiceFactory.class, connection.getComponentContext().getServiceManager());
466         return xMSF1;
467     }
468 
469     // setup and close connections
470     @BeforeClass
setUpConnection()471     public static void setUpConnection() throws Exception
472     {
473         System.out.println("setUpConnection()");
474         connection.setUp();
475     }
476 
477     @AfterClass
tearDownConnection()478     public static void tearDownConnection()
479             throws InterruptedException, com.sun.star.uno.Exception
480     {
481         System.out.println("tearDownConnection()");
482         connection.tearDown();
483     }
484     private static final OfficeConnection connection = new OfficeConnection();
485 }
486