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