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