001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.report2;
017
018import org.opengion.fukurou.system.OgRuntimeException ;                 // 6.4.2.0 (2016/01/29)
019import java.io.File;
020import java.io.IOException;
021
022import org.opengion.fukurou.util.FileUtil;
023import org.opengion.fukurou.util.StringUtil;
024import org.opengion.fukurou.system.HybsConst;                                   // 7.2.3.1 (2020/04/17)
025import org.opengion.fukurou.system.ThrowUtil;                                   // 6.4.2.0 (2016/01/29)
026import org.opengion.hayabusa.common.HybsSystem;
027import org.opengion.hayabusa.common.HybsSystemException;
028import static org.opengion.fukurou.system.HybsConst.FS ;                // 8.0.3.0 (2021/12/17)
029
030import com.sun.star.bridge.UnoUrlResolver;
031import com.sun.star.bridge.XUnoUrlResolver;
032import com.sun.star.comp.helper.Bootstrap;
033import com.sun.star.comp.helper.BootstrapException;
034import com.sun.star.frame.XDesktop;
035import com.sun.star.frame.XDispatchHelper;
036import com.sun.star.lang.XMultiComponentFactory;
037import com.sun.star.uno.UnoRuntime;
038import com.sun.star.uno.XComponentContext;
039import com.sun.star.connection.ConnectionSetupException;                // 6.3.9.0 (2015/11/06)
040
041/**
042 * OpenOfficeのプロセスを表すクラスです。
043 *
044 * bootstrap()メソッドが呼ばれたタイミングでsoffice.binのプロセスを生成します。
045 * soffice.binのプロセスを引数なしで実装した場合、通常は各ユーザーで1プロセスしか
046 * 生成されないため、-env:UserInstallationの引数を指定することで、仮想的に別ユーザー
047 * として起動しています。
048 * この"ユーザー"を表すキーは、コンストラクタの引数のidです。
049 *
050 * また、この仮想ユーザーで起動した場合、初回起動時にユーザー登録を促す画面が立ち上がります。
051 * これを回避するため、デフォルトの環境ファイルをプロセス生成前にコピーすることで、認証済みの
052 * 状態で立ち上がるようにしています。
053 *
054 * 起動したプロセスとの通知は名前付きパイプで行われます。パイプ名は、"env"+コンストラクタのidです。
055 * プロセス起動と、名前付きパイプでの接続は非同期で行われます。
056 * プロセス起動後、60秒経過しても接続できない場合は、BootstrapExceptionが発生します。
057 *
058 * @version  4.0
059 * @author   Hiroki Nakamura
060 * @since    JDK5.0,
061 */
062public class SOfficeProcess {
063        /** OOoのインストールディレクトリ  */
064        public static final String OFFICE_HOME =
065//              new File( System.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + File.separator;
066//              new File( HybsConst.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + File.separator;       // 7.2.3.1 (2020/04/17)
067                new File( HybsConst.getenv( "OFFICE_HOME" ) ).getAbsolutePath() + FS;                           // 8.0.3.0 (2021/12/17)
068
069        /** 環境設定のパス */
070        // 5.1.7.0 (2010/06/01) 複数サーバー対応漏れ
071        public static final String ENV_DIR =
072                HybsSystem.url2dir( StringUtil.nval( HybsSystem.sys( "REPORT_FILE_URL" )
073                                                        , HybsSystem.sys( "FILE_URL" ) + "REPORT" + FS ) + "oooenv" ) + FS;
074        /** 設定ファイルの雛形 */
075        private static final String DEFAULT_ENV_PATH =
076//              OFFICE_HOME + "env" + File.separator + "_default";
077                OFFICE_HOME + "env" + FS + "_default";                                  // 8.0.3.0 (2021/12/17)
078
079        /** soffice.binのパス */
080        private static final String SOFFICE_BIN =
081//              OFFICE_HOME + File.separator + "program" + File.separator + "soffice.bin";
082                OFFICE_HOME + FS + "program" + FS + "soffice.bin";              // 8.0.3.0 (2021/12/17)
083
084        /** ローカルコンテキスト */
085        private static XComponentContext xLocalContext ;
086
087        private final String envPath;
088        private final String envId;                             // 環境設定ファイルのID
089
090        /** リモートデスクトップインスタンス */
091        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
092        private XDesktop desktop        ;
093
094        private XComponentContext remoteContext ;
095
096        /** soffice.binのプロセス */
097        private Process process         ;
098
099        static {
100                try {
101                        xLocalContext = Bootstrap.createInitialComponentContext( null );
102                }
103                catch( final Throwable th ) {
104                        System.out.println( "[ERROR]PROCESS:Can't start LocalContext,Check OFFICE_HOME!" );
105                        System.err.println( ThrowUtil.ogStackTrace( th ) );                             // 6.4.2.0 (2016/01/29)
106                }
107        }
108
109        /**
110         * コンストラクタです。
111         *
112         * @og.rev 4.3.0.0 (2008/07/15) 設定ファイルを各コンテキストごとに置くように変更
113         * @param       id      プロセスID
114         */
115        protected SOfficeProcess( final String id ) {
116                envId = id;
117                // envPath = OFFICE_HOME + "env" + File.separator + envId;
118                envPath = ENV_DIR + envId;
119        }
120
121        /**
122         * OOoへの接続を行います。
123         *
124         * @og.rev 5.0.0.0 (2009/08/03) Linux対応(パイプ名に":"が含まれていると接続できない)
125         * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
126         */
127        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
128        protected void bootstrap() {
129                System.out.println( "[INFO]OOo:Starting soffice process,ENV-ID=" + envId );
130
131                // check enviroment files, if no files, create from default files
132                checkEnv( envPath );
133
134                // pipe name
135                // 4.3.3.6 (2008/11/15) マルチサーバ対応。同一サーバでの複数実行時不具合のため。
136                // 5.0.0.0 (2009/08/03) Linux対応
137                //String sPipeName = "uno" + envId;
138                final String sPipeName = "uno" + "_" + HybsSystem.sys("HOST_URL").replace(':','_').replace('/','_') + "_" + envId;
139
140                // start office process
141                // 5.5.2.4 (2012/05/16) int priority は使われていないので、削除します。
142                process = execOffice( envPath, sPipeName );
143                System.out.println( "[INFO]OOo:Invoke soffice.bin,ENV-ID=" + envId );
144
145                // create a URL resolver
146                final XUnoUrlResolver xUrlResolver = UnoUrlResolver.create( xLocalContext );
147
148                // connection string
149                // 5.1.7.0 (2010/06/01) TCP接続対応
150                final String sConnect = getConnParam( sPipeName );
151
152                // wait until office is started
153                try {
154                        for( int i=0;; ++i ) {
155                                try {
156                                        final Object context = xUrlResolver.resolve( sConnect );
157                                        remoteContext = (XComponentContext) UnoRuntime.queryInterface( XComponentContext.class, context );
158                                        if( remoteContext == null ) { throw new BootstrapException( "no component context!" ); }
159                                        break;
160                                }
161                                catch( final com.sun.star.connection.NoConnectException ex ) {
162                                        System.out.println( "[INFO]OOo:Waiting for Connect soffice process,ENV-ID=" + envId );
163                                        if( i == 60 ) { throw new BootstrapException( ex ); }
164                                        Thread.sleep( 1000 );
165                                }
166                        }
167
168                        // create desktop instance
169                        final XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
170                        desktop = (XDesktop) UnoRuntime.queryInterface( XDesktop.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.Desktop", remoteContext ) );
171                }
172                catch( final ConnectionSetupException
173                                | BootstrapException
174                                | IllegalArgumentException
175                                | InterruptedException ex ) {
176                        throw new HybsSystemException( "[ERROR]PROCESS:Can't create Desktop Instance", ex );
177                }
178//              catch( final Exception ex ) {
179                catch( final Throwable th ) {           // PMD : 6.9.9.4 (2018/10/01)
180                        throw new HybsSystemException( "[ERROR]PROCESS:Can't create XDesktop Instance", th );
181                }
182
183                System.out.println( "[INFO]OOo:Connected successful,ENV-ID=" + envId );
184        }
185
186        /**
187         * Pipe名をキーにOpenOfficeのプロセスに接続するための文字列を生成します。
188         *
189         * @param key Pipe名
190         *
191         * @return 接続文字列
192         * @og.rtnNotNull
193         */
194        protected String getConnParam( final String key ) {
195                return "uno:pipe,name=" + key + ";urp;StarOffice.ComponentContext";
196        }
197
198        /**
199         * デスクトップインスタンスを返します。
200         *
201         * @return デスクトップインスタンス
202         */
203        public XDesktop getDesktop() {
204                return desktop;
205        }
206
207        /**
208         * プロセスを終了します。
209         * また、同時に環境設定用のファイルも削除します。
210         *
211         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
212         *
213         */
214        public void close() {
215                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
216                if( process == null ) {
217                        final String errMsg = "#bootstrap()を先に実行しておいてください。" ;
218                        throw new OgRuntimeException( errMsg );
219                }
220
221                process.destroy();
222                FileUtil.deleteFiles( new File( envPath ) );
223                System.out.println( "[INFO]OOo:Destroy process,ENV-ID=" + envId );
224        }
225
226        /**
227         * soffice.binを起動します。
228         *
229         * @og.rev 5.1.7.0 (2010/06/01) TCP接続対応
230         * @og.rev 5.5.2.4 (2012/05/16) int priority は使われていないので、削除します。
231         *
232         * @param envPath 環境変数パス
233         * @param pipeName パイプ名
234         *
235         * @return soffice.binのプロセス
236         */
237        private Process execOffice( final String envPath, final String pipeName ) {
238                String[] cmdArray = new String[11];
239                cmdArray[0] = SOFFICE_BIN;
240                cmdArray[1] = "-nologo";
241                cmdArray[2] = "-nodefault";
242                cmdArray[3] = "-norestore";
243                cmdArray[4] = "-nocrashreport";
244                cmdArray[5] = "-nolockcheck";
245                cmdArray[6] = "-minimized";
246                cmdArray[7] = "-invisible";
247                cmdArray[8] = "-headless";
248                cmdArray[9] = "-env:UserInstallation=file:///" + ( envPath ).replace( '\\', '/' );
249                // 5.1.7.0 (2010/06/01) TCP接続対応
250                cmdArray[10] = getProcParam( pipeName );
251
252                Process process;
253                try {
254                        process = Runtime.getRuntime().exec( cmdArray );
255                } catch( final IOException ex ) {
256                        throw new HybsSystemException( "[ERROR]PROCESS:Cant't exec soffice.bin", ex );
257                }
258
259                return process;
260        }
261
262        /**
263         * Pipe名をキーにOpenOfficeのプロセスを生成するためのパラメーター文字列を生成します。
264         *
265         * @param key Pipe名
266         *
267         * @return プロセス生成パラメーター
268         * @og.rtnNotNull
269         */
270        protected String getProcParam( final String key ) {
271                return "-accept=pipe,name=" + key + ";urp;";
272        }
273
274        /**
275         * OOoの環境設定をチェックします。
276         *
277         * ※ OFFICE_HOMEが設定されていない場合、HybsSystemException が、throw されます。
278         *
279         * @og.rev 4.3.0.0 (2008/07/24) OS依存をやめてJavaでコピーする
280         *
281         * @param envPath 環境設定のパス
282         */
283        private void checkEnv( final String envPath ) {
284
285                if( OFFICE_HOME == null || OFFICE_HOME.isEmpty() ) {
286                        throw new HybsSystemException( "OFFICE_HOMEが設定されていないため、OpenOfficeを起動できません" );
287                }
288
289                // 4.3.0.0 (2008/07/24) OS依存からFileUtilを使うように変更
290                FileUtil.copyDirectry( DEFAULT_ENV_PATH, envPath );
291
292                // 5.1.7.0 (2010/06/01) ファイルマージ対応
293                if( ! ( new File( getTempPath() ) ).mkdirs() ) {
294                        System.err.println( "ファイルマージ時のテンポラリフォルダを作成できませんでした。[" + getTempPath() + "]" );
295                }
296        }
297
298        /**
299         * OpenOfficeのローカルコンポーネントコンテキストを返します。
300         *
301         * @og.rev 5.1.7.0 (2010/06/01) 新規作成
302         * @og.rev 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
303         *
304         * @return ローカルコンポーネントコンテキスト
305         */
306        @SuppressWarnings("cast")       // OpenOffice 3.2 での冗長なキャスト警告の抑止。キャストをはずすと、旧3.1 では、エラーになる。
307        public XDispatchHelper getDispatcher() {
308                // 6.3.9.0 (2015/11/06) コンストラクタで初期化されていないフィールドを null チェックなしで利用している(findbugs)
309                if( remoteContext == null ) {
310                        final String errMsg = "#bootstrap()を先に実行しておいてください。" ;
311                        throw new OgRuntimeException( errMsg );
312                }
313
314                final XMultiComponentFactory componentFactory = remoteContext.getServiceManager();
315                XDispatchHelper dispatcher = null;
316                try {
317                        dispatcher = (XDispatchHelper) UnoRuntime.queryInterface( XDispatchHelper.class, componentFactory.createInstanceWithContext( "com.sun.star.frame.DispatchHelper", remoteContext ) );
318                }
319                catch( final com.sun.star.uno.Exception ex ) {
320                        throw new HybsSystemException( "ディスパッチャーの取得に失敗しました。", ex );
321                }
322                return dispatcher;
323        }
324
325        /**
326         * このプロセスに対して固有に使用できる一時ファイルのパスを指定します。
327         *
328         * @og.rev 5.1.7.0 (2010/06/01) 新規作成
329         *
330         * @return 一時ファイルのパス
331         * @og.rtnNotNull
332         */
333        public String getTempPath() {
334//              return envPath + File.separator + "temp" + File.separator;
335                return envPath + FS + "temp" + FS;              // 8.0.3.0 (2021/12/17)
336        }
337}