    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}